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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/LICENSE.md +1 -1
- data/README.md +11 -6
- data/jsi.gemspec +30 -0
- data/lib/jsi/base/node.rb +183 -0
- data/lib/jsi/base.rb +135 -161
- data/lib/jsi/jsi_coder.rb +3 -3
- data/lib/jsi/metaschema.rb +0 -1
- data/lib/jsi/metaschema_node/bootstrap_schema.rb +9 -8
- data/lib/jsi/metaschema_node.rb +48 -51
- data/lib/jsi/ptr.rb +28 -17
- data/lib/jsi/schema/application/child_application/contains.rb +11 -2
- data/lib/jsi/schema/application/child_application/items.rb +3 -3
- data/lib/jsi/schema/application/child_application/properties.rb +3 -3
- data/lib/jsi/schema/application/child_application.rb +1 -3
- data/lib/jsi/schema/application/inplace_application/dependencies.rb +1 -1
- data/lib/jsi/schema/application/inplace_application/ifthenelse.rb +3 -3
- data/lib/jsi/schema/application/inplace_application/ref.rb +1 -1
- data/lib/jsi/schema/application/inplace_application/someof.rb +26 -11
- data/lib/jsi/schema/application/inplace_application.rb +1 -6
- data/lib/jsi/schema/ref.rb +3 -2
- data/lib/jsi/schema/schema_ancestor_node.rb +11 -17
- data/lib/jsi/schema/validation/array.rb +3 -3
- data/lib/jsi/schema/validation/const.rb +1 -1
- data/lib/jsi/schema/validation/contains.rb +1 -1
- data/lib/jsi/schema/validation/dependencies.rb +1 -1
- data/lib/jsi/schema/validation/draft04/minmax.rb +6 -6
- data/lib/jsi/schema/validation/enum.rb +1 -1
- data/lib/jsi/schema/validation/ifthenelse.rb +5 -5
- data/lib/jsi/schema/validation/items.rb +4 -4
- data/lib/jsi/schema/validation/not.rb +1 -1
- data/lib/jsi/schema/validation/numeric.rb +5 -5
- data/lib/jsi/schema/validation/object.rb +2 -2
- data/lib/jsi/schema/validation/pattern.rb +1 -1
- data/lib/jsi/schema/validation/properties.rb +3 -3
- data/lib/jsi/schema/validation/property_names.rb +1 -1
- data/lib/jsi/schema/validation/ref.rb +1 -1
- data/lib/jsi/schema/validation/required.rb +1 -1
- data/lib/jsi/schema/validation/someof.rb +3 -3
- data/lib/jsi/schema/validation/string.rb +2 -2
- data/lib/jsi/schema/validation/type.rb +1 -1
- data/lib/jsi/schema/validation.rb +1 -1
- data/lib/jsi/schema.rb +91 -85
- data/lib/jsi/schema_classes.rb +70 -45
- data/lib/jsi/schema_registry.rb +15 -5
- data/lib/jsi/schema_set.rb +42 -2
- data/lib/jsi/simple_wrap.rb +23 -4
- data/lib/jsi/util/{attr_struct.rb → private/attr_struct.rb} +40 -19
- data/lib/jsi/util/private.rb +204 -0
- data/lib/jsi/{typelike_modules.rb → util/typelike.rb} +56 -82
- data/lib/jsi/util.rb +68 -148
- data/lib/jsi/version.rb +1 -1
- data/lib/jsi.rb +1 -17
- data/lib/schemas/json-schema.org/draft-04/schema.rb +3 -1
- data/lib/schemas/json-schema.org/draft-06/schema.rb +3 -1
- data/lib/schemas/json-schema.org/draft-07/schema.rb +3 -1
- metadata +11 -9
- 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
|
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
|
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
|
151
|
-
#
|
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
|
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
|
213
|
-
# Schema#
|
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::
|
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')
|
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(
|
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
|
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
|
-
|
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.
|
354
|
+
resource.schema_absolute_uri
|
349
355
|
end
|
350
356
|
|
351
|
-
anchored =
|
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 =
|
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
|
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
|
-
|
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
|
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
|
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
|
-
|
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
|
-
|
504
|
-
|
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
|
-
|
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:
|
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?(
|
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?(
|
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
|
606
|
+
jsi_schema_resource_ancestors.dup.push(self).freeze
|
601
607
|
else
|
602
608
|
jsi_schema_resource_ancestors
|
603
609
|
end
|
data/lib/jsi/schema_classes.rb
CHANGED
@@ -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
|
32
|
-
schema.new_jsi(instance, **kw
|
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
|
-
# @
|
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)
|
94
|
-
define_singleton_method(:
|
95
|
-
define_method(:
|
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
|
110
|
-
|
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::
|
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
|
156
|
-
|
157
|
-
|
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
|
170
|
-
self[property_name,
|
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
|
-
|
196
|
-
|
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
|
-
|
201
|
-
|
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
|
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
|
-
|
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
|
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::
|
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::
|
275
|
+
# @param node [JSI::Base]
|
249
276
|
def initialize(node)
|
250
|
-
unless node.is_a?(JSI::
|
251
|
-
|
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
|
data/lib/jsi/schema_registry.rb
CHANGED
@@ -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
|
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.
|
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
|
-
|
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
|
-
|
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
|