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 that describes other schemas, i.e. is a meta-schema (a MetaSchema). Therefore, any JSI instance described by a schema which is a MetaSchema 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, Draft04, Draft06, Draft07, IdWithAnchor, MetaSchema, OldId, SchemaAncestorNode, Validation Classes: Error, Issue, NotASchemaError, Ref, ReferenceError

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.ensure_metaschema(metaschema, name: nil, schema_registry: JSI.schema_registry) ⇒ Base + Schema + Schema::MetaSchema

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 meta-schema and returns that meta-schema.

Parameters:

Returns:

Raises:

  • (TypeError)

    if the param does not indicate a meta-schema



418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
# File 'lib/jsi/schema.rb', line 418

def ensure_metaschema(metaschema, name: nil, schema_registry: JSI.schema_registry)
  if metaschema.respond_to?(:to_str)
    schema = Schema::Ref.new(metaschema, schema_registry: schema_registry).deref_schema
    if !schema.describes_schema?
      raise(TypeError, [name, "URI indicates a schema that is not a meta-schema: #{metaschema.pretty_inspect.chomp}"].compact.join(" "))
    end
    schema
  elsif metaschema.is_a?(SchemaModule::MetaSchemaModule)
    metaschema.schema
  elsif metaschema.is_a?(Schema::MetaSchema)
    metaschema
  else
    raise(TypeError, "#{name || "param"} does not indicate a meta-schema: #{metaschema.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:



379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
# File 'lib/jsi/schema.rb', line 379

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_indicated_schemas = SchemaSet.new(schema.jsi_indicated_schemas + reinstantiate_as)
      result_schema_applied_schemas = result_schema_indicated_schemas.inplace_applicator_schemas(schema.jsi_node_content)

      result_schema_class = JSI::SchemaClasses.class_for_schemas(result_schema_applied_schemas,
        includes: SchemaClasses.includes_for(schema.jsi_node_content),
        mutable: schema.jsi_mutable?,
      )

      result_schema_class.new(schema.jsi_document,
        jsi_ptr: schema.jsi_ptr,
        jsi_indicated_schemas: result_schema_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_content_to_immutable: schema.jsi_content_to_immutable,
        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



692
693
694
# File 'lib/jsi/schema.rb', line 692

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)


714
715
716
# File 'lib/jsi/schema.rb', line 714

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 schemas, i.e. it is a meta-schema. this schema is extended with MetaSchema and its #jsi_schema_module is extended with JSI::SchemaModule::MetaSchemaModule, 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.



588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
# File 'lib/jsi/schema.rb', line 588

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::MetaSchemaModule)
  end

  @schema_implementation_modules = schema_implementation_modules
  extend(Schema::MetaSchema)

  nil
end

#describes_schema?Boolean

Does this schema itself describe a schema? I.e. is this schema a meta-schema?

Returns:

  • (Boolean)


570
571
572
# File 'lib/jsi/schema.rb', line 570

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



702
703
704
705
706
707
708
# File 'lib/jsi/schema.rb', line 702

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: Util::EMPTY_ARY) {|JSI::Schema| ... } ⇒ nil, Enumerator

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

Parameters:

  • visited_refs (Enumerable<JSI::Schema::Ref>) (defaults to: Util::EMPTY_ARY)
  • instance (Object)

    the instance to check any applicators against

Yields:

Returns:

  • (nil, Enumerator)

    an Enumerator if invoked without a block; otherwise nil



671
672
673
674
675
676
677
678
679
680
681
682
683
# File 'lib/jsi/schema.rb', line 671

def each_inplace_applicator_schema(
    instance,
    visited_refs: Util::EMPTY_ARY,
    &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)


497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
# File 'lib/jsi/schema.rb', line 497

def each_schema_uri
  return to_enum(__method__) unless block_given?

  yield schema_absolute_uri if schema_absolute_uri

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

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

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

  nil
end

#initializeObject



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

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:



661
662
663
# File 'lib/jsi/schema.rb', line 661

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)


747
748
749
750
751
752
# File 'lib/jsi/schema.rb', line 747

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:



733
734
735
736
737
738
739
740
741
742
# File 'lib/jsi/schema.rb', line 733

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_is_schema?Boolean

Is this a JSI Schema?

Returns:

  • (Boolean)


576
577
578
# File 'lib/jsi/schema.rb', line 576

def jsi_is_schema?
  true
end

#jsi_schema_moduleSchemaModule

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:



536
537
538
# File 'lib/jsi/schema.rb', line 536

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



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

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:



804
805
806
807
808
809
810
# File 'lib/jsi/schema.rb', line 804

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)


456
457
458
459
# File 'lib/jsi/schema.rb', line 456

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::MetaSchema#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.

  • to_immutable (#call, nil)

    A proc/callable which takes given instance content and results in an immutable (i.e. deeply frozen) object equal to that. If the instantiated JSI will be mutable, this is not used. Though not recommended, this may be nil with immutable JSIs if the instance content is otherwise guaranteed to be immutable, as well as any modified copies of the instance.

  • mutable (Boolean)

    Whether the instantiated JSI will be mutable. The instance content will be transformed with to_immutable if the JSI will be immutable.

Returns:

  • (JSI::Base subclass)

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



557
558
559
# File 'lib/jsi/schema.rb', line 557

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:



647
648
649
650
651
652
# File 'lib/jsi/schema.rb', line 647

def resource_root_subschema(ptr)
      ptr = Ptr.ary_ptr(ptr)
      Schema.ensure_schema(schema_resource_root.jsi_descendent_node(ptr),
        reinstantiate_as: jsi_schemas.select(&:describes_schema?)
      )
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)


463
464
465
466
467
468
469
470
471
472
473
474
# File 'lib/jsi/schema.rb', line 463

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.



450
451
452
# File 'lib/jsi/schema.rb', line 450

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)


563
564
565
566
# File 'lib/jsi/schema.rb', line 563

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 ancestor, 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 ancestor 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



621
622
623
# File 'lib/jsi/schema.rb', line 621

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)


627
628
629
# File 'lib/jsi/schema.rb', line 627

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 ancestor of this schema defines an id. see #schema_uris for all URIs known to refer to this schema.

Returns:

  • (Addressable::URI, nil)


480
481
482
# File 'lib/jsi/schema.rb', line 480

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


486
487
488
# File 'lib/jsi/schema.rb', line 486

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.



635
636
637
638
639
640
# File 'lib/jsi/schema.rb', line 635

def subschema(subptr)
      subptr = Ptr.ary_ptr(subptr)
      Schema.ensure_schema(jsi_descendent_node(subptr), msg: [
        "subschema is not a schema at pointer: #{subptr.pointer}"
      ])
end