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/schema.rb CHANGED
@@ -8,12 +8,12 @@ module JSI
8
8
  module Schema
9
9
  autoload :Application, 'jsi/schema/application'
10
10
  autoload :Validation, 'jsi/schema/validation'
11
+
11
12
  autoload :Issue, 'jsi/schema/issue'
13
+ autoload :Ref, 'jsi/schema/ref'
12
14
 
13
15
  autoload :SchemaAncestorNode, 'jsi/schema/schema_ancestor_node'
14
16
 
15
- autoload :Ref, 'jsi/schema/ref'
16
-
17
17
  autoload :Draft04, 'jsi/schema/draft04'
18
18
  autoload :Draft06, 'jsi/schema/draft06'
19
19
  autoload :Draft07, 'jsi/schema/draft07'
@@ -34,7 +34,7 @@ module JSI
34
34
  # the contents of a $id keyword whose value is a string, or nil
35
35
  # @return [#to_str, nil]
36
36
  def id
37
- if schema_content.respond_to?(:to_hash) && schema_content['$id'].respond_to?(:to_str)
37
+ if keyword?('$id') && schema_content['$id'].respond_to?(:to_str)
38
38
  schema_content['$id']
39
39
  else
40
40
  nil
@@ -47,7 +47,7 @@ module JSI
47
47
  # the contents of an `id` keyword whose value is a string, or nil
48
48
  # @return [#to_str, nil]
49
49
  def id
50
- if schema_content.respond_to?(:to_hash) && schema_content['id'].respond_to?(:to_str)
50
+ if keyword?('id') && schema_content['id'].respond_to?(:to_str)
51
51
  schema_content['id']
52
52
  else
53
53
  nil
@@ -147,8 +147,8 @@ module JSI
147
147
  # @param uri [nil, #to_str, Addressable::URI] the URI of the schema document.
148
148
  # relative URIs within the document are resolved using this uri as their base.
149
149
  # the result schema will be registered with this URI in the {JSI.schema_registry}.
150
- # @return [JSI::Base, JSI::Schema] a JSI whose instance is the given schema_content and whose schemas
151
- # are inplace applicators matched from self to the schema being instantiated.
150
+ # @return [JSI::Base] a JSI which is a {JSI::Schema} whose instance is the given `schema_content`
151
+ # and whose schemas are this schema's inplace applicators.
152
152
  def new_schema(schema_content,
153
153
  uri: nil
154
154
  )
@@ -176,7 +176,7 @@ module JSI
176
176
  # @return [nil, #new_schema]
177
177
  def default_metaschema
178
178
  return @default_metaschema if instance_variable_defined?(:@default_metaschema)
179
- return JSONSchemaOrgDraft07
179
+ return nil
180
180
  end
181
181
 
182
182
  # sets an application-wide default metaschema used by {JSI.new_schema}
@@ -209,8 +209,8 @@ module JSI
209
209
  # `JSI::JSONSchemaOrgDraft07.new_schema(my_schema_object)`
210
210
  #
211
211
  # if the given schema_object is a JSI::Base but not already a JSI::Schema, an error
212
- # will be raised. schemas which describe schemas must have JSI::Schema in their
213
- # Schema#jsi_schema_instance_modules.
212
+ # will be raised. schemas which describe schemas must include JSI::Schema in their
213
+ # {Schema#jsi_schema_module}.
214
214
  #
215
215
  # @param schema_object [#to_hash, Boolean, JSI::Schema] an object to be instantiated as a schema.
216
216
  # if it's already a JSI::Schema, it is returned as-is.
@@ -218,7 +218,8 @@ module JSI
218
218
  # @param default_metaschema [#new_schema] the metaschema to use if the schema_object does not have
219
219
  # a '$schema' property. this may be a metaschema or a metaschema's schema module
220
220
  # (e.g. `JSI::JSONSchemaOrgDraft07`).
221
- # @return [JSI::Schema] a JSI::Schema representing the given schema_object
221
+ # @return [JSI::Base] a JSI which is a {JSI::Schema} whose instance is the given `schema_object`
222
+ # and whose schemas are the metaschema's inplace applicators.
222
223
  def new_schema(schema_object, default_metaschema: nil, **kw)
223
224
  default_metaschema_new_schema = -> {
224
225
  default_metaschema ||= JSI::Schema.default_metaschema
@@ -241,7 +242,10 @@ module JSI
241
242
  elsif schema_object.is_a?(JSI::Base)
242
243
  raise(NotASchemaError, "the given schema_object is a JSI::Base, but is not a JSI::Schema: #{schema_object.pretty_inspect.chomp}")
243
244
  elsif schema_object.respond_to?(:to_hash)
244
- if schema_object.key?('$schema') && schema_object['$schema'].respond_to?(:to_str)
245
+ if schema_object.key?('$schema')
246
+ unless schema_object['$schema'].respond_to?(:to_str)
247
+ raise(ArgumentError, "given schema_object keyword `$schema` is not a string")
248
+ end
245
249
  metaschema = Schema::Ref.new(schema_object['$schema']).deref_schema
246
250
  unless metaschema.describes_schema?
247
251
  raise(Schema::ReferenceError, "given schema_object contains a $schema but the resource it identifies does not describe a schema")
@@ -257,12 +261,6 @@ module JSI
257
261
  end
258
262
  end
259
263
 
260
- # @deprecated
261
- alias_method :new, :new_schema
262
-
263
- # @deprecated
264
- alias_method :from_object, :new_schema
265
-
266
264
  # ensure the given object is a JSI Schema
267
265
  #
268
266
  # @param schema [Object] the thing the caller wishes to ensure is a Schema
@@ -279,10 +277,11 @@ module JSI
279
277
 
280
278
  result_schema_schemas = schema.jsi_schemas + reinstantiate_as
281
279
 
282
- result_schema_class = JSI::SchemaClasses.class_for_schemas(result_schema_schemas)
280
+ result_schema_class = JSI::SchemaClasses.class_for_schemas(result_schema_schemas,
281
+ includes: SchemaClasses.includes_for(schema.jsi_node_content)
282
+ )
283
283
 
284
- result_schema_class.new(Base::NOINSTANCE,
285
- jsi_document: schema.jsi_document,
284
+ result_schema_class.new(schema.jsi_document,
286
285
  jsi_ptr: schema.jsi_ptr,
287
286
  jsi_root_node: schema.jsi_root_node,
288
287
  jsi_schema_base_uri: schema.jsi_schema_base_uri,
@@ -299,18 +298,25 @@ module JSI
299
298
  end
300
299
 
301
300
  # the underlying JSON data used to instantiate this JSI::Schema.
302
- # this is an alias for PathedNode#jsi_node_content, named for clarity in the context of working with
301
+ # this is an alias for {Base#jsi_node_content}, named for clarity in the context of working with
303
302
  # a schema.
304
303
  def schema_content
305
304
  jsi_node_content
306
305
  end
307
306
 
307
+ # does this schema contain the given keyword?
308
+ # @return [Boolean]
309
+ def keyword?(keyword)
310
+ schema_content = jsi_node_content
311
+ schema_content.respond_to?(:to_hash) && schema_content.key?(keyword)
312
+ end
313
+
308
314
  # the URI of this schema, calculated from our `#id`, resolved against our `#jsi_schema_base_uri`
309
315
  # @return [Addressable::URI, nil]
310
316
  def schema_absolute_uri
311
317
  if respond_to?(:id_without_fragment) && id_without_fragment
312
318
  if jsi_schema_base_uri
313
- Addressable::URI.parse(jsi_schema_base_uri).join(id_without_fragment)
319
+ jsi_schema_base_uri.join(id_without_fragment)
314
320
  elsif id_without_fragment.absolute?
315
321
  id_without_fragment
316
322
  else
@@ -345,10 +351,10 @@ module JSI
345
351
  yield schema_absolute_uri if schema_absolute_uri
346
352
 
347
353
  parent_schemas = jsi_subschema_resource_ancestors.reverse_each.select do |resource|
348
- resource.is_a?(Schema) && resource.schema_absolute_uri
354
+ resource.schema_absolute_uri
349
355
  end
350
356
 
351
- anchored = self.anchor
357
+ anchored = respond_to?(:anchor) ? anchor : nil
352
358
  parent_schemas.each do |parent_schema|
353
359
  if anchored
354
360
  if parent_schema.jsi_anchor_subschema(anchor) == self
@@ -358,7 +364,7 @@ module JSI
358
364
  end
359
365
  end
360
366
 
361
- relative_ptr = self.jsi_ptr.ptr_relative_to(parent_schema.jsi_ptr)
367
+ relative_ptr = jsi_ptr.relative_to(parent_schema.jsi_ptr)
362
368
  yield parent_schema.schema_absolute_uri.merge(fragment: relative_ptr.fragment)
363
369
  end
364
370
 
@@ -395,48 +401,80 @@ module JSI
395
401
  jsi_schema_module.module_exec(*a, **kw, &block)
396
402
  end
397
403
 
398
- # @private @deprecated
399
- def jsi_schema_class
400
- JSI::SchemaClasses.class_for_schemas(SchemaSet[self])
401
- end
402
-
403
404
  # instantiates the given instance as a JSI::Base class for schemas matched from this schema to the
404
405
  # instance.
405
406
  #
406
407
  # @param instance [Object] the JSON Schema instance to be represented as a JSI
407
408
  # @param uri (see SchemaSet#new_jsi)
408
- # @return [JSI::Base subclass] a JSI whose instance is the given instance and whose schemas are matched
409
- # from this schema.
410
- def new_jsi(instance,
411
- **kw
412
- )
409
+ # @return [JSI::Base subclass] a JSI whose instance is the given instance and whose schemas are
410
+ # inplace applicator schemas matched from this schema.
411
+ def new_jsi(instance, **kw)
413
412
  SchemaSet[self].new_jsi(instance, **kw)
414
413
  end
415
414
 
416
415
  # does this schema itself describe a schema?
417
416
  # @return [Boolean]
418
417
  def describes_schema?
419
- jsi_schema_instance_modules.any? { |m| m <= JSI::Schema }
418
+ jsi_schema_module <= JSI::Schema ||
419
+ # deprecated
420
+ jsi_schema_instance_modules.any? { |m| m <= JSI::Schema }
420
421
  end
421
422
 
422
423
  # modules to apply to instances described by this schema. these modules are included
423
424
  # on this schema's {#jsi_schema_module}
425
+ # @deprecated reopen the jsi_schema_module to include such modules instead, or use describes_schema! if this schema describes a schema
424
426
  # @return [Set<Module>]
425
427
  def jsi_schema_instance_modules
426
428
  return @jsi_schema_instance_modules if instance_variable_defined?(:@jsi_schema_instance_modules)
427
- return Set[].freeze
429
+ return Util::EMPTY_SET
428
430
  end
429
431
 
430
432
  # see {#jsi_schema_instance_modules}
431
433
  #
434
+ # @deprecated
432
435
  # @return [void]
433
436
  def jsi_schema_instance_modules=(jsi_schema_instance_modules)
434
437
  @jsi_schema_instance_modules = Util.ensure_module_set(jsi_schema_instance_modules)
435
438
  end
436
439
 
440
+ # indicates that this schema describes a schema.
441
+ # this schema is extended with {DescribesSchema} and its {#jsi_schema_module} is extended
442
+ # with {DescribesSchemaModule}, and the JSI Schema Module will include the given modules.
443
+ #
444
+ # @param schema_implementation_modules [Enumerable<Module>] modules which implement the functionality of
445
+ # the schema to extend schemas described by this schema.
446
+ # this must include JSI::Schema (usually indirectly).
447
+ # @return [void]
448
+ def describes_schema!(schema_implementation_modules)
449
+ schema_implementation_modules = Util.ensure_module_set(schema_implementation_modules)
450
+
451
+ unless schema_implementation_modules.any? { |mod| mod <= Schema }
452
+ raise(ArgumentError, "schema_implementation_modules for a schema must include #{Schema}")
453
+ end
454
+
455
+ if describes_schema?
456
+ # this schema, or one equal to it, has already had describes_schema! called on it.
457
+ # this is to be avoided, but is not particularly a problem.
458
+ # it is a bug if it was called different times with different schema_implementation_modules, though.
459
+ unless jsi_schema_module.schema_implementation_modules == schema_implementation_modules
460
+ raise(ArgumentError, "this schema already describes a schema with different schema_implementation_modules")
461
+ end
462
+ else
463
+ schema_implementation_modules.each do |mod|
464
+ jsi_schema_module.include(mod)
465
+ end
466
+ jsi_schema_module.extend(DescribesSchemaModule)
467
+ jsi_schema_module.instance_variable_set(:@schema_implementation_modules, schema_implementation_modules)
468
+ end
469
+
470
+ extend(DescribesSchema)
471
+
472
+ nil
473
+ end
474
+
437
475
  # a resource containing this schema.
438
476
  #
439
- # if any parent, or this schema itself, is a schema with an absolute uri (see #schema_absolute_uri),
477
+ # if any parent, or this schema itself, is a schema with an absolute uri (see {#schema_absolute_uri}),
440
478
  # the resource root is the closest schema with an absolute uri.
441
479
  #
442
480
  # if no parent schema has an absolute uri, the schema_resource_root is the root of the document
@@ -458,13 +496,13 @@ module JSI
458
496
  # @param subptr [JSI::Ptr, #to_ary] a relative pointer, or array of tokens, pointing to the subschema
459
497
  # @return [JSI::Schema] the subschema at the location indicated by subptr. self if subptr is empty.
460
498
  def subschema(subptr)
461
- subschema_map[Ptr.ary_ptr(subptr)]
499
+ subschema_map[subptr: Ptr.ary_ptr(subptr)]
462
500
  end
463
501
 
464
502
  private
465
503
 
466
504
  def subschema_map
467
- jsi_memomap(:subschema) do |subptr|
505
+ jsi_memomap(:subschema) do |subptr: |
468
506
  if is_a?(MetaschemaNode::BootstrapSchema)
469
507
  self.class.new(
470
508
  jsi_document,
@@ -472,7 +510,7 @@ module JSI
472
510
  jsi_schema_base_uri: jsi_resource_ancestor_uri,
473
511
  )
474
512
  else
475
- Schema.ensure_schema(subptr.evaluate(self, as_jsi: true), msg: [
513
+ Schema.ensure_schema(jsi_descendent_node(subptr), msg: [
476
514
  "subschema is not a schema at pointer: #{subptr.pointer}"
477
515
  ])
478
516
  end
@@ -481,37 +519,35 @@ module JSI
481
519
 
482
520
  public
483
521
 
484
- # a schema in the same schema resource as this one (see #schema_resource_root) at the given
522
+ # a schema in the same schema resource as this one (see {#schema_resource_root}) at the given
485
523
  # pointer relative to the root of the schema resource.
486
524
  #
487
525
  # @param ptr [JSI::Ptr, #to_ary] a pointer to a schema from our schema resource root
488
526
  # @return [JSI::Schema] the schema pointed to by ptr
489
527
  def resource_root_subschema(ptr)
490
- resource_root_subschema_map[Ptr.ary_ptr(ptr)]
528
+ resource_root_subschema_map[ptr: Ptr.ary_ptr(ptr)]
491
529
  end
492
530
 
493
531
  private
494
532
 
495
533
  def resource_root_subschema_map
496
- jsi_memomap(:resource_root_subschema_map) do |ptr|
497
- schema = self
498
- if schema.is_a?(MetaschemaNode::BootstrapSchema)
534
+ jsi_memomap(:resource_root_subschema_map) do |ptr: |
535
+ if is_a?(MetaschemaNode::BootstrapSchema)
499
536
  # BootstrapSchema does not track jsi_schema_resource_ancestors used by #schema_resource_root;
500
537
  # resource_root_subschema is always relative to the document root.
501
538
  # BootstrapSchema also does not implement jsi_root_node or #[]. we instantiate the ptr directly
502
539
  # rather than as a subschema from the root.
503
- schema.class.new(
504
- schema.jsi_document,
540
+ self.class.new(
541
+ jsi_document,
505
542
  jsi_ptr: ptr,
506
543
  jsi_schema_base_uri: nil,
507
544
  )
508
545
  else
509
- resource_root = schema.schema_resource_root
510
- Schema.ensure_schema(ptr.evaluate(resource_root, as_jsi: true),
546
+ Schema.ensure_schema(schema_resource_root.jsi_descendent_node(ptr),
511
547
  msg: [
512
548
  "subschema is not a schema at pointer: #{ptr.pointer}"
513
549
  ],
514
- reinstantiate_as: schema.jsi_schemas.select(&:describes_schema?)
550
+ reinstantiate_as: jsi_schemas.select(&:describes_schema?)
515
551
  )
516
552
  end
517
553
  end
@@ -541,7 +577,7 @@ module JSI
541
577
  # @param instance [Object] the instance to validate against this schema
542
578
  # @return [JSI::Validation::Result]
543
579
  def instance_validate(instance)
544
- if instance.is_a?(JSI::PathedNode)
580
+ if instance.is_a?(Base)
545
581
  instance_ptr = instance.jsi_ptr
546
582
  instance_document = instance.jsi_document
547
583
  else
@@ -555,49 +591,19 @@ module JSI
555
591
  # @param instance [Object] the instance to validate against this schema
556
592
  # @return [Boolean]
557
593
  def instance_valid?(instance)
558
- if instance.is_a?(JSI::PathedNode)
594
+ if instance.is_a?(Base)
559
595
  instance = instance.jsi_node_content
560
596
  end
561
597
  internal_validate_instance(Ptr[], instance, validate_only: true).valid?
562
598
  end
563
599
 
564
- # @private
565
- def fully_validate_instance(other_instance, errors_as_objects: false)
566
- raise(NotImplementedError, "Schema#fully_validate_instance removed: see new validation interface Schema#instance_validate")
567
- end
568
-
569
- # @private
570
- def validate_instance(other_instance)
571
- raise(NotImplementedError, "Schema#validate_instance renamed: see Schema#instance_valid?")
572
- end
573
-
574
- # @private
575
- def validate_instance!(other_instance)
576
- raise(NotImplementedError, "Schema#validate_instance! removed")
577
- end
578
-
579
- # @private
580
- def fully_validate_schema(errors_as_objects: false)
581
- raise(NotImplementedError, "Schema#fully_validate_schema removed: use validation interface Base#jsi_validate on the schema")
582
- end
583
-
584
- # @private
585
- def validate_schema
586
- raise(NotImplementedError, "Schema#validate_schema removed: use validation interface Base#jsi_valid? on the schema")
587
- end
588
-
589
- # @private
590
- def validate_schema!
591
- raise(NotImplementedError, "Schema#validate_schema! removed")
592
- end
593
-
594
600
  # schema resources which are ancestors of any subschemas below this schema.
595
601
  # this may include this schema if this is a schema resource root.
596
- # @private
602
+ # @api private
597
603
  # @return [Array<JSI::Schema>]
598
604
  def jsi_subschema_resource_ancestors
599
605
  if schema_resource_root?
600
- jsi_schema_resource_ancestors + [self]
606
+ jsi_schema_resource_ancestors.dup.push(self).freeze
601
607
  else
602
608
  jsi_schema_resource_ancestors
603
609
  end
@@ -24,12 +24,14 @@ module JSI
24
24
  end
25
25
  end
26
26
 
27
+ alias_method :to_s, :inspect
28
+
27
29
  # invokes {JSI::Schema#new_jsi} on this module's schema, passing the given instance.
28
30
  #
29
31
  # @param (see JSI::Schema#new_jsi)
30
32
  # @return [JSI::Base] a JSI whose instance is the given instance
31
- def new_jsi(instance, **kw, &b)
32
- schema.new_jsi(instance, **kw, &b)
33
+ def new_jsi(instance, **kw)
34
+ schema.new_jsi(instance, **kw)
33
35
  end
34
36
  end
35
37
 
@@ -55,6 +57,9 @@ module JSI
55
57
  def new_schema_module(schema_content, **kw)
56
58
  schema.new_schema(schema_content, **kw).jsi_schema_module
57
59
  end
60
+
61
+ # @return [Set<Module>]
62
+ attr_reader :schema_implementation_modules
58
63
  end
59
64
 
60
65
  # this module is a namespace for building schema classes and schema modules.
@@ -62,18 +67,30 @@ module JSI
62
67
  extend Util::Memoize
63
68
 
64
69
  class << self
70
+ # @api private
71
+ # @return [Set<Module>]
72
+ def includes_for(instance)
73
+ includes = Set[]
74
+ includes << Base::ArrayNode if instance.respond_to?(:to_ary)
75
+ includes << Base::HashNode if instance.respond_to?(:to_hash)
76
+ includes.freeze
77
+ end
78
+
65
79
  # a JSI Schema Class which represents the given schemas.
66
80
  # an instance of the class is a JSON Schema instance described by all of the given schemas.
67
- # @private
81
+ # @api private
68
82
  # @param schemas [Enumerable<JSI::Schema>] schemas which the class will represent
83
+ # @param includes [Enumerable<Module>] modules which will be included on the class
69
84
  # @return [Class subclassing JSI::Base]
70
- def class_for_schemas(schemas)
85
+ def class_for_schemas(schemas, includes: )
71
86
  schemas = SchemaSet.ensure_schema_set(schemas)
87
+ includes = Util.ensure_module_set(includes)
72
88
 
73
- jsi_memoize(:class_for_schemas, schemas) do |schemas|
74
- Class.new(Base).instance_exec(schemas) do |schemas|
89
+ jsi_memoize(:class_for_schemas, schemas: schemas, includes: includes) do |schemas: , includes: |
90
+ Class.new(Base).instance_exec(schemas: schemas, includes: includes) do |schemas: , includes: |
75
91
  define_singleton_method(:jsi_class_schemas) { schemas }
76
92
  define_method(:jsi_schemas) { schemas }
93
+ includes.each { |m| include(m) }
77
94
  schemas.each { |schema| include(schema.jsi_schema_module) }
78
95
  jsi_class = self
79
96
  define_method(:jsi_class) { jsi_class }
@@ -83,16 +100,16 @@ module JSI
83
100
  end
84
101
  end
85
102
 
86
- # @private
87
103
  # a subclass of MetaschemaNode::BootstrapSchema with the given modules included
88
- # @param modules [Set<Module>] metaschema instance modules
104
+ # @api private
105
+ # @param modules [Set<Module>] schema implementation modules
89
106
  # @return [Class]
90
107
  def bootstrap_schema_class(modules)
91
108
  modules = Util.ensure_module_set(modules)
92
- jsi_memoize(__method__, modules) do |modules|
93
- Class.new(MetaschemaNode::BootstrapSchema).instance_exec(modules) do |modules|
94
- define_singleton_method(:metaschema_instance_modules) { modules }
95
- define_method(:metaschema_instance_modules) { modules }
109
+ jsi_memoize(__method__, modules: modules) do |modules: |
110
+ Class.new(MetaschemaNode::BootstrapSchema) do
111
+ define_singleton_method(:schema_implementation_modules) { modules }
112
+ define_method(:schema_implementation_modules) { modules }
96
113
  modules.each { |mod| include(mod) }
97
114
 
98
115
  self
@@ -105,9 +122,9 @@ module JSI
105
122
  # @return [Module]
106
123
  def module_for_schema(schema)
107
124
  Schema.ensure_schema(schema)
108
- jsi_memoize(:module_for_schema, schema) do |schema|
109
- Module.new.tap do |m|
110
- m.module_eval do
125
+ jsi_memoize(:module_for_schema, schema: schema) do |schema: |
126
+ Module.new do
127
+ begin
111
128
  define_singleton_method(:schema) { schema }
112
129
 
113
130
  extend SchemaModule
@@ -117,17 +134,13 @@ module JSI
117
134
  end
118
135
 
119
136
  accessor_module = JSI::SchemaClasses.accessor_module_for_schema(schema,
120
- conflicting_modules: Set[JSI::Base, JSI::PathedArrayNode, JSI::PathedHashNode] +
137
+ conflicting_modules: Set[JSI::Base, JSI::Base::ArrayNode, JSI::Base::HashNode] +
121
138
  schema.jsi_schema_instance_modules,
122
139
  )
123
140
  include accessor_module
124
141
 
125
142
  define_singleton_method(:jsi_property_accessors) { accessor_module.jsi_property_accessors }
126
143
 
127
- if schema.describes_schema?
128
- extend DescribesSchemaModule
129
- end
130
-
131
144
  @possibly_schema_node = schema
132
145
  extend(SchemaModulePossibly)
133
146
  schema.jsi_schemas.each do |schema_schema|
@@ -143,6 +156,8 @@ module JSI
143
156
 
144
157
  # a module of accessors for described property names of the given schema.
145
158
  # getters are always defined. setters are defined by default.
159
+ #
160
+ # @api private
146
161
  # @param schema [JSI::Schema] a schema for which to define accessors for any described property names
147
162
  # @param conflicting_modules [Enumerable<Module>] an array of modules (or classes) which
148
163
  # may be used alongside the accessor module. methods defined by any conflicting_module
@@ -151,10 +166,12 @@ module JSI
151
166
  # @return [Module]
152
167
  def accessor_module_for_schema(schema, conflicting_modules: , setters: true)
153
168
  Schema.ensure_schema(schema)
154
- jsi_memoize(:accessor_module_for_schema, schema, conflicting_modules, setters) do |schema, conflicting_modules, setters|
155
- Module.new.tap do |m|
156
- m.module_eval do
157
- conflicting_instance_methods = (conflicting_modules + [m]).map do |mod|
169
+ jsi_memoize(:accessor_module_for_schema, schema: schema, conflicting_modules: conflicting_modules, setters: setters) do |schema: , conflicting_modules: , setters: |
170
+ Module.new do
171
+ begin
172
+ define_singleton_method(:inspect) { '(JSI Schema Accessor Module)' }
173
+
174
+ conflicting_instance_methods = conflicting_modules.map do |mod|
158
175
  mod.instance_methods + mod.private_instance_methods
159
176
  end.inject(Set.new, &:|)
160
177
 
@@ -166,8 +183,8 @@ module JSI
166
183
  define_singleton_method(:jsi_property_accessors) { accessors_to_define }
167
184
 
168
185
  accessors_to_define.each do |property_name|
169
- define_method(property_name) do |*a|
170
- self[property_name, *a]
186
+ define_method(property_name) do |**kw|
187
+ self[property_name, **kw]
171
188
  end
172
189
  if setters
173
190
  define_method("#{property_name}=") do |value|
@@ -190,45 +207,55 @@ module JSI
190
207
  # a name relative to a named schema module of an ancestor schema.
191
208
  # for example, if `Foos = JSI::JSONSchemaOrgDraft07.new_schema_module({'items' => {}})`
192
209
  # then the module `Foos.items` will have a name_from_ancestor of `"Foos.items"`
210
+ # @api private
193
211
  # @return [String, nil]
194
212
  def name_from_ancestor
195
- schema_ancestors = [possibly_schema_node] + possibly_schema_node.jsi_parent_nodes
196
- named_parent_schema = schema_ancestors.detect { |jsi| jsi.is_a?(JSI::Schema) && jsi.jsi_schema_module.name }
197
-
198
- return nil unless named_parent_schema
213
+ named_ancestor_schema, tokens = named_ancestor_schema_tokens
214
+ return nil unless named_ancestor_schema
199
215
 
200
- tokens = possibly_schema_node.jsi_ptr.ptr_relative_to(named_parent_schema.jsi_ptr).tokens
201
- name = named_parent_schema.jsi_schema_module.name
202
- parent = named_parent_schema
216
+ name = named_ancestor_schema.jsi_schema_module.name
217
+ ancestor = named_ancestor_schema
203
218
  tokens.each do |token|
204
- if parent.jsi_schemas.any? { |s| s.jsi_schema_module.jsi_property_accessors.include?(token) }
219
+ if ancestor.jsi_schemas.any? { |s| s.jsi_schema_module.jsi_property_accessors.include?(token) }
205
220
  name += ".#{token}"
206
221
  elsif [String, Numeric, TrueClass, FalseClass, NilClass].any? { |m| token.is_a?(m) }
207
222
  name += "[#{token.inspect}]"
208
223
  else
209
224
  return nil
210
225
  end
211
- parent = parent[token]
226
+ ancestor = ancestor[token]
212
227
  end
213
228
  name
214
229
  end
215
230
 
216
231
  # subscripting a JSI schema module or a NotASchemaModule will subscript the node, and
217
- # if the result is a JSI::Schema, return the JSI Schema module of that schema; if it is a PathedNode,
232
+ # if the result is a JSI::Schema, return the JSI Schema module of that schema; if it is a JSI::Base,
218
233
  # return a NotASchemaModule; or if it is another value (a basic type), return that value.
219
234
  #
220
235
  # @param token [Object]
221
236
  # @return [Module, NotASchemaModule, Object]
222
- def [](token)
237
+ def [](token, **kw)
238
+ raise(ArgumentError) unless kw.empty? # TODO remove eventually (keyword argument compatibility)
223
239
  sub = @possibly_schema_node[token]
224
240
  if sub.is_a?(JSI::Schema)
225
241
  sub.jsi_schema_module
226
- elsif sub.is_a?(JSI::PathedNode)
242
+ elsif sub.is_a?(JSI::Base)
227
243
  NotASchemaModule.new(sub)
228
244
  else
229
245
  sub
230
246
  end
231
247
  end
248
+
249
+ private
250
+
251
+ # @return [Array<JSI::Schema, Array>, nil]
252
+ def named_ancestor_schema_tokens
253
+ schema_ancestors = possibly_schema_node.jsi_ancestor_nodes
254
+ named_ancestor_schema = schema_ancestors.detect { |jsi| jsi.is_a?(JSI::Schema) && jsi.jsi_schema_module.name }
255
+ return nil unless named_ancestor_schema
256
+ tokens = possibly_schema_node.jsi_ptr.relative_to(named_ancestor_schema.jsi_ptr).tokens
257
+ [named_ancestor_schema, tokens]
258
+ end
232
259
  end
233
260
 
234
261
  # a JSI Schema Module is a module which represents a schema. a NotASchemaModule represents
@@ -245,14 +272,10 @@ module JSI
245
272
  # is another node, a NotASchemaModule for that node is returned. otherwise - when the
246
273
  # value is a basic type - that value itself is returned.
247
274
  class NotASchemaModule
248
- # @param node [JSI::PathedNode]
275
+ # @param node [JSI::Base]
249
276
  def initialize(node)
250
- unless node.is_a?(JSI::PathedNode)
251
- raise(TypeError, "not JSI::PathedNode: #{node.pretty_inspect.chomp}")
252
- end
253
- if node.is_a?(JSI::Schema)
254
- raise(TypeError, "cannot instantiate NotASchemaModule for a JSI::Schema node: #{node.pretty_inspect.chomp}")
255
- end
277
+ raise(Bug, "node must be JSI::Base: #{node.pretty_inspect.chomp}") unless node.is_a?(JSI::Base)
278
+ raise(Bug, "node must not be JSI::Schema: #{node.pretty_inspect.chomp}") if node.is_a?(JSI::Schema)
256
279
  @possibly_schema_node = node
257
280
  node.jsi_schemas.each do |schema|
258
281
  extend(JSI::SchemaClasses.accessor_module_for_schema(schema, conflicting_modules: [NotASchemaModule, SchemaModulePossibly], setters: false))
@@ -269,5 +292,7 @@ module JSI
269
292
  "(JSI wrapper for Schema Module: #{@possibly_schema_node.jsi_ptr.uri})"
270
293
  end
271
294
  end
295
+
296
+ alias_method :to_s, :inspect
272
297
  end
273
298
  end
@@ -24,8 +24,8 @@ module JSI
24
24
 
25
25
  # registers the given resource and/or schema resources it contains in the registry.
26
26
  #
27
- # each child of the resource (including the resource itself) is registered if it is a schema that
28
- # has an absolute URI (generally defined by the '$id' keyword).
27
+ # each descendent node of the resource (including the resource itself) is registered if it is a schema
28
+ # that has an absolute URI (generally defined by the '$id' keyword).
29
29
  #
30
30
  # the given resource itself will be registered, whether or not it is a schema, if it is the root
31
31
  # of its document and was instantiated with the option `uri` specified.
@@ -48,7 +48,7 @@ module JSI
48
48
  register_single(resource.jsi_schema_base_uri, resource)
49
49
  end
50
50
 
51
- resource.jsi_each_child_node do |node|
51
+ resource.jsi_each_descendent_node do |node|
52
52
  if node.is_a?(JSI::Schema) && node.schema_absolute_uri
53
53
  register_single(node.schema_absolute_uri, node)
54
54
  end
@@ -89,11 +89,21 @@ module JSI
89
89
  uri = Addressable::URI.parse(uri)
90
90
  ensure_uri_absolute(uri)
91
91
  if @autoload_uris.key?(uri) && !@resources.key?(uri)
92
- register(@autoload_uris[uri].call)
92
+ autoloaded = @autoload_uris[uri].call
93
+ register(autoloaded)
93
94
  end
94
95
  registered_uris = @resources.keys
95
96
  if !registered_uris.include?(uri)
96
- raise(ResourceNotFound, "URI #{uri} is not registered. registered URIs:\n#{registered_uris.join("\n")}")
97
+ if @autoload_uris.key?(uri)
98
+ msg = [
99
+ "URI #{uri} was registered with autoload_uri but the result did not contain a resource with that URI.",
100
+ "the resource resulting from autoload_uri was:",
101
+ autoloaded.pretty_inspect.chomp,
102
+ ]
103
+ else
104
+ msg = ["URI #{uri} is not registered. registered URIs:", *registered_uris]
105
+ end
106
+ raise(ResourceNotFound, msg.join("\n"))
97
107
  end
98
108
  @resources[uri]
99
109
  end