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/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