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



411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
# File 'lib/jsi/schema.rb', line 411

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:



372
373
374
375
376
377
378
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
# File 'lib/jsi/schema.rb', line 372

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



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

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)


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

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.



581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
# File 'lib/jsi/schema.rb', line 581

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)


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

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



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

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



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

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)


490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
# File 'lib/jsi/schema.rb', line 490

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



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

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:



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

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)


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

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:



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

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)


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

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:



529
530
531
# File 'lib/jsi/schema.rb', line 529

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



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

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:



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

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)


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

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.



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

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:



643
644
645
# File 'lib/jsi/schema.rb', line 643

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)


456
457
458
459
460
461
462
463
464
465
466
467
# File 'lib/jsi/schema.rb', line 456

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.



443
444
445
# File 'lib/jsi/schema.rb', line 443

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)


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

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



614
615
616
# File 'lib/jsi/schema.rb', line 614

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)


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

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)


473
474
475
# File 'lib/jsi/schema.rb', line 473

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


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

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.



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

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