jsi 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|