Module: JSI::Schema

Included in:
MetaschemaNode::BootstrapSchema
Defined in:
lib/jsi/schema.rb,
lib/jsi/schema.rb,
lib/jsi/schema/issue.rb,
lib/jsi/schema/draft04.rb,
lib/jsi/schema/draft06.rb,
lib/jsi/schema/draft07.rb

Overview

JSI::Schema is a module which extends Base instances which represent JSON schemas.

This module is included on the JSI Schema module of any schema which describes other schemas, i.e. is a metaschema or other DescribesSchema. Therefore, any JSI instance described by a schema which is a DescribesSchema is a schema and is extended by this module.

The content of an instance which is a JSI::Schema (referred to in this context as schema_content) is typically a Hash (JSON object) or a boolean.

Defined Under Namespace

Modules: Application, BigMoneyId, DescribesSchema, Draft04, Draft06, Draft07, IdWithAnchor, OldId, SchemaAncestorNode, Validation Classes: Error, Issue, NotASchemaError, Ref, ReferenceError

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.ensure_describes_schema(describes_schema, name: nil, schema_registry: JSI.schema_registry) ⇒ Base + Schema + Schema::DescribesSchema

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Ensures the given param identifies a JSI Schema which describes schemas, and returns that schema.

Parameters:

Returns:

Raises:

  • (TypeError)

    if the param does not indicate a schema which describes schemas



399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
# File 'lib/jsi/schema.rb', line 399

def ensure_describes_schema(describes_schema, name: nil, schema_registry: JSI.schema_registry)
  if describes_schema.respond_to?(:to_str)
    schema = Schema::Ref.new(describes_schema, schema_registry: schema_registry).deref_schema
    if !schema.describes_schema?
      raise(TypeError, [name, "URI indicates a schema which does not describe schemas: #{describes_schema.pretty_inspect.chomp}"].compact.join(" "))
    end
    schema
  elsif describes_schema.is_a?(SchemaModule::DescribesSchemaModule)
    describes_schema.schema
  elsif describes_schema.is_a?(DescribesSchema)
    describes_schema
  else
    raise(TypeError, "#{name || "param"} does not indicate a schema which describes schemas: #{describes_schema.pretty_inspect.chomp}")
  end
end

.ensure_schema(schema, msg: "indicated object is not a schema:", reinstantiate_as: nil) ⇒ Schema

ensure the given object is a JSI Schema

Parameters:

  • schema (Object)

    the thing the caller wishes to ensure is a Schema

  • msg (#to_s, #to_ary) (defaults to: "indicated object is not a schema:")

    lines of the error message preceding the pretty-printed schema param if the schema param is not a schema

Returns:

  • (Schema)

    the given schema

Raises:



363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
# File 'lib/jsi/schema.rb', line 363

def ensure_schema(schema, msg: "indicated object is not a schema:", reinstantiate_as: nil)
  if schema.is_a?(Schema)
    schema
  else
    if reinstantiate_as && schema.is_a?(JSI::Base)
      # TODO warn; behavior is undefined and I hate this implementation

      result_schema_schemas = schema.jsi_schemas + reinstantiate_as

      result_schema_class = JSI::SchemaClasses.class_for_schemas(result_schema_schemas,
        includes: SchemaClasses.includes_for(schema.jsi_node_content)
      )

      result_schema_class.new(schema.jsi_document,
        jsi_ptr: schema.jsi_ptr,
        jsi_indicated_schemas: schema.jsi_indicated_schemas,
        jsi_schema_base_uri: schema.jsi_schema_base_uri,
        jsi_schema_resource_ancestors: schema.jsi_schema_resource_ancestors,
        jsi_schema_registry: schema.jsi_schema_registry,
        jsi_root_node: schema.jsi_ptr.root? ? nil : schema.jsi_root_node, # bad
      )
    else
      raise(NotASchemaError, [
        *msg,
        schema.pretty_inspect.chomp,
      ].join("\n"))
    end
  end
end

Instance Method Details

#child_applicator_schemas(token, instance) ⇒ JSI::SchemaSet

a set of child applicator subschemas of this schema which apply to the child of the given instance on the given token.

Parameters:

  • token (Object)

    the array index or object property name for the child instance

  • instance (Object)

    the instance to check any child applicators against

Returns:

  • (JSI::SchemaSet)

    child applicator subschemas of this schema for the given token of the instance



672
673
674
# File 'lib/jsi/schema.rb', line 672

def child_applicator_schemas(token, instance)
  SchemaSet.new(each_child_applicator_schema(token, instance))
end

#described_object_property_namesSet

any object property names this schema indicates may be present on its instances. this includes any keys of this schema's "properties" object and any entries of this schema's array of "required" property keys.

Returns:

  • (Set)


694
695
696
# File 'lib/jsi/schema.rb', line 694

def described_object_property_names
  @described_object_property_names_map[]
end

#describes_schema!(schema_implementation_modules)

This method returns an undefined value.

indicates that this schema describes a schema. this schema is extended with DescribesSchema and its #jsi_schema_module is extended with JSI::SchemaModule::DescribesSchemaModule, and the JSI Schema Module will include JSI::Schema and the given modules.

Parameters:

  • schema_implementation_modules (Enumerable<Module>)

    modules which implement the functionality of the schema to extend schemas described by this schema.



563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
# File 'lib/jsi/schema.rb', line 563

def describes_schema!(schema_implementation_modules)
  schema_implementation_modules = Util.ensure_module_set(schema_implementation_modules)

  if describes_schema?
    # this schema, or one equal to it, has already had describes_schema! called on it.
    # this is to be avoided, but is not particularly a problem.
    # it is a bug if it was called different times with different schema_implementation_modules, though.
    unless jsi_schema_module.schema_implementation_modules == schema_implementation_modules
      raise(ArgumentError, "this schema already describes a schema with different schema_implementation_modules")
    end
  else
    jsi_schema_module.include(Schema)
    schema_implementation_modules.each do |mod|
      jsi_schema_module.include(mod)
    end
    jsi_schema_module.extend(SchemaModule::DescribesSchemaModule)
    jsi_schema_module.instance_variable_set(:@schema_implementation_modules, schema_implementation_modules)
  end

  extend(DescribesSchema)

  nil
end

#describes_schema?Boolean

does this schema itself describe a schema?

Returns:

  • (Boolean)


551
552
553
# File 'lib/jsi/schema.rb', line 551

def describes_schema?
  jsi_schema_module <= JSI::Schema || false
end

#each_child_applicator_schema(token, instance) {|JSI::Schema| ... } ⇒ nil, Enumerator

yields each child applicator subschema (from properties, items, etc.) which applies to the child of the given instance on the given token.

Parameters:

  • token (Object)

    the array index or object property name for the child instance

  • instance (Object)

    the instance to check any child applicators against

Yields:

Returns:

  • (nil, Enumerator)

    an Enumerator if invoked without a block; otherwise nil



682
683
684
685
686
687
688
# File 'lib/jsi/schema.rb', line 682

def each_child_applicator_schema(token, instance, &block)
  return to_enum(__method__, token, instance) unless block

  internal_child_applicate_keywords(token, instance, &block)

  nil
end

#each_inplace_applicator_schema(instance, visited_refs: []) {|JSI::Schema| ... } ⇒ nil, Enumerator

yields each inplace applicator schema which applies to the given instance.

Parameters:

  • visited_refs (Enumerable<JSI::Schema::Ref>) (defaults to: [])
  • instance (Object)

    the instance to check any applicators against

Yields:

Returns:

  • (nil, Enumerator)

    an Enumerator if invoked without a block; otherwise nil



655
656
657
658
659
660
661
662
663
# File 'lib/jsi/schema.rb', line 655

def each_inplace_applicator_schema(instance, visited_refs: [], &block)
  return to_enum(__method__, instance, visited_refs: visited_refs) unless block

  catch(:jsi_application_done) do
    internal_inplace_applicate_keywords(instance, visited_refs, &block)
  end

  nil
end

#each_schema_uri {|Addressable::URI| ... } ⇒ Enumerator?

Yields:

  • (Addressable::URI)

Returns:

  • (Enumerator, nil)


478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
# File 'lib/jsi/schema.rb', line 478

def each_schema_uri
  return to_enum(__method__) unless block_given?

  yield schema_absolute_uri if schema_absolute_uri

  parent_schemas = jsi_subschema_resource_ancestors.reverse_each.select do |resource|
    resource.schema_absolute_uri
  end

  anchored = respond_to?(:anchor) ? anchor : nil
  parent_schemas.each do |parent_schema|
    if anchored
      if parent_schema.jsi_anchor_subschema(anchor) == self
        yield parent_schema.schema_absolute_uri.merge(fragment: anchor).freeze
      else
        anchored = false
      end
    end

    relative_ptr = jsi_ptr.relative_to(parent_schema.jsi_ptr)
    yield parent_schema.schema_absolute_uri.merge(fragment: relative_ptr.fragment).freeze
  end

  nil
end

#initializeObject



417
418
419
420
# File 'lib/jsi/schema.rb', line 417

def initialize(*)
  super
  jsi_schema_initialize
end

#inplace_applicator_schemas(instance) ⇒ JSI::SchemaSet

a set of inplace applicator schemas of this schema (from $ref, allOf, etc.) which apply to the given instance.

the returned set will contain this schema itself, unless this schema contains a $ref keyword.

Parameters:

  • instance (Object)

    the instance to check any applicators against

Returns:



645
646
647
# File 'lib/jsi/schema.rb', line 645

def inplace_applicator_schemas(instance)
  SchemaSet.new(each_inplace_applicator_schema(instance))
end

#instance_valid?(instance) ⇒ Boolean

whether the given instance is valid against this schema

Parameters:

  • instance (Object)

    the instance to validate against this schema

Returns:

  • (Boolean)


727
728
729
730
731
732
# File 'lib/jsi/schema.rb', line 727

def instance_valid?(instance)
  if instance.is_a?(SchemaAncestorNode)
    instance = instance.jsi_node_content
  end
  internal_validate_instance(Ptr[], instance, validate_only: true).valid?
end

#instance_validate(instance) ⇒ JSI::Validation::Result

validates the given instance against this schema

Parameters:

  • instance (Object)

    the instance to validate against this schema

Returns:



713
714
715
716
717
718
719
720
721
722
# File 'lib/jsi/schema.rb', line 713

def instance_validate(instance)
  if instance.is_a?(SchemaAncestorNode)
    instance_ptr = instance.jsi_ptr
    instance_document = instance.jsi_document
  else
    instance_ptr = Ptr[]
    instance_document = instance
  end
  internal_validate_instance(instance_ptr, instance_document)
end

#jsi_schema_moduleModule + SchemaModule

a module which extends all instances of this schema. this may be opened by the application to add methods to schema instances.

some functionality is also defined on the module itself (its singleton class, not for its instances):

  • the module is extended with JSI::SchemaModule, which defines .new_jsi to instantiate instances of this schema (see #new_jsi).
  • properties described by this schema's metaschema are defined as methods to get subschemas' schema modules, so for example schema.jsi_schema_module.items returns the same module as schema.items.jsi_schema_module.
  • method .schema which returns this schema.

Returns:



517
518
519
# File 'lib/jsi/schema.rb', line 517

def jsi_schema_module
  JSI::SchemaClasses.module_for_schema(self)
end

#jsi_schema_module_exec(*a, **kw, &block) ⇒ Object

Evaluates the given block in the context of this schema's JSI schema module. Any arguments passed to this method will be passed to the block. shortcut to invoke Module#module_exec on our #jsi_schema_module.

Returns:

  • the result of evaluating the block



527
528
529
# File 'lib/jsi/schema.rb', line 527

def jsi_schema_module_exec(*a, **kw, &block)
  jsi_schema_module.module_exec(*a, **kw, &block)
end

#jsi_subschema_resource_ancestorsArray<JSI::Schema>

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

schema resources which are ancestors of any subschemas below this schema. this may include this schema if this is a schema resource root.

Returns:



772
773
774
775
776
777
778
# File 'lib/jsi/schema.rb', line 772

def jsi_subschema_resource_ancestors
  if schema_resource_root?
    jsi_schema_resource_ancestors.dup.push(self).freeze
  else
    jsi_schema_resource_ancestors
  end
end

#keyword?(keyword) ⇒ Boolean

does this schema contain the given keyword?

Returns:

  • (Boolean)


437
438
439
440
# File 'lib/jsi/schema.rb', line 437

def keyword?(keyword)
  schema_content = jsi_node_content
  schema_content.respond_to?(:to_hash) && schema_content.key?(keyword)
end

#new_jsi(instance, **kw) ⇒ JSI::Base subclass

Instantiates a new JSI whose content comes from the given instance param. This schema indicates the schemas of the JSI - its schemas are inplace applicators of this schema which apply to the given instance.

Parameters:

  • instance (Object)

    the instance to be represented as a JSI

  • uri (#to_str, Addressable::URI)

    The retrieval URI of the instance.

    It is rare that this needs to be specified, and only useful for instances which contain schemas. See JSI::Schema::DescribesSchema#new_schema's uri param documentation.

  • register (Boolean)

    Whether schema resources in the instantiated JSI will be registered in the schema registry indicated by param schema_registry. This is only useful when the JSI is a schema or contains schemas. The JSI's root will be registered with the uri param, if specified, whether or not the root is a schema.

  • schema_registry (SchemaRegistry, nil)

    The registry to use for references to other schemas and, depending on register and uri params, to register this JSI and/or any contained schemas with declared URIs.

  • stringify_symbol_keys (Boolean)

    Whether the instance content will have any Symbol keys of Hashes replaced with Strings (recursively through the document). Replacement is done on a copy; the given instance is not modified.

Returns:

  • (JSI::Base subclass)

    a JSI whose content comes from the given instance and whose schemas are inplace applicators of this schema.



538
539
540
# File 'lib/jsi/schema.rb', line 538

def new_jsi(instance, **kw)
  SchemaSet[self].new_jsi(instance, **kw)
end

#resource_root_subschema(ptr) ⇒ JSI::Schema

a schema in the same schema resource as this one (see #schema_resource_root) at the given pointer relative to the root of the schema resource.

Parameters:

  • ptr (JSI::Ptr, #to_ary)

    a pointer to a schema from our schema resource root

Returns:



625
626
627
# File 'lib/jsi/schema.rb', line 625

def resource_root_subschema(ptr)
  @resource_root_subschema_map[ptr: Ptr.ary_ptr(ptr)]
end

#schema_absolute_uriAddressable::URI?

the URI of this schema, calculated from our #id, resolved against our #jsi_schema_base_uri

Returns:

  • (Addressable::URI, nil)


444
445
446
447
448
449
450
451
452
453
454
455
# File 'lib/jsi/schema.rb', line 444

def schema_absolute_uri
  if respond_to?(:id_without_fragment) && id_without_fragment
    if jsi_schema_base_uri
      jsi_schema_base_uri.join(id_without_fragment).freeze
    elsif id_without_fragment.absolute?
      id_without_fragment
    else
      # TODO warn / schema_error
      nil
    end
  end
end

#schema_contentObject

the underlying JSON data used to instantiate this JSI::Schema. this is an alias for Base#jsi_node_content, named for clarity in the context of working with a schema.



431
432
433
# File 'lib/jsi/schema.rb', line 431

def schema_content
  jsi_node_content
end

#schema_ref(keyword: "$ref") ⇒ Schema::Ref

Parameters:

  • keyword (defaults to: "$ref")

    schema keyword e.g. "$ref", "$schema"

Returns:

Raises:

  • (ArgumentError)


544
545
546
547
# File 'lib/jsi/schema.rb', line 544

def schema_ref(keyword: "$ref")
  raise(ArgumentError, "keyword not present: #{keyword}") unless keyword?(keyword)
  @schema_ref_map[keyword: keyword, value: schema_content[keyword]]
end

#schema_resource_rootJSI::Base

a resource containing this schema.

if any parent, or this schema itself, is a schema with an absolute uri (see #schema_absolute_uri), the resource root is the closest schema with an absolute uri.

If no parent schema has an absolute uri, the schema_resource_root is the document's root node. In this case, the resource root may or may not be a schema itself.

Returns:

  • (JSI::Base)

    resource containing this schema



596
597
598
# File 'lib/jsi/schema.rb', line 596

def schema_resource_root
  jsi_subschema_resource_ancestors.last || jsi_root_node
end

#schema_resource_root?Boolean

is this schema the root of a schema resource?

Returns:

  • (Boolean)


602
603
604
# File 'lib/jsi/schema.rb', line 602

def schema_resource_root?
  jsi_ptr.root? || !!schema_absolute_uri
end

#schema_uriAddressable::URI?

a nonrelative URI which refers to this schema. nil if no parent of this schema defines an id. see #schema_uris for all URIs known to refer to this schema.

Returns:

  • (Addressable::URI, nil)


461
462
463
# File 'lib/jsi/schema.rb', line 461

def schema_uri
  schema_uris.first
end

#schema_urisArray<Addressable::URI>

nonrelative URIs (that is, absolute, but possibly with a fragment) which refer to this schema

Returns:

  • (Array<Addressable::URI>)


467
468
469
# File 'lib/jsi/schema.rb', line 467

def schema_uris
  @schema_uris_map[]
end

#subschema(subptr) ⇒ JSI::Schema

a subschema of this Schema

Parameters:

  • subptr (JSI::Ptr, #to_ary)

    a relative pointer, or array of tokens, pointing to the subschema

Returns:

  • (JSI::Schema)

    the subschema at the location indicated by subptr. self if subptr is empty.



610
611
612
# File 'lib/jsi/schema.rb', line 610

def subschema(subptr)
  @subschema_map[subptr: Ptr.ary_ptr(subptr)]
end