Module: JSI::Schema

Included in:
MetaSchemaNode::BootstrapSchema
Defined in:
lib/jsi/schema.rb,
lib/jsi/schema.rb,
lib/jsi/schema/cxt.rb,
lib/jsi/schema/issue.rb,
lib/jsi/schema/dialect.rb,
lib/jsi/schema/draft04.rb,
lib/jsi/schema/draft06.rb,
lib/jsi/schema/draft07.rb,
lib/jsi/schema/element.rb,
lib/jsi/schema/vocabulary.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: Draft04, Draft06, Draft07, Draft202012, Elements, MetaSchema, SchemaAncestorNode Classes: Cxt, Dialect, DynamicAnchorMap, Element, Issue, NotAMetaSchemaError, NotASchemaError, Ref, Vocabulary

Constant Summary collapse

ReferenceError =
Deprecated.

alias after v0.8

an exception raised when we are unable to resolve a schema reference

ResolutionError

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.ensure_metaschema(metaschema, name: nil, registry: JSI.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



342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
# File 'lib/jsi/schema.rb', line 342

def ensure_metaschema(metaschema, name: nil, registry: JSI.registry)
  if metaschema.respond_to?(:to_str)
    schema = Schema::Ref.new(metaschema, registry: registry).resolve
    if !schema.describes_schema?
      raise(NotAMetaSchemaError, [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(NotAMetaSchemaError, "#{name || "param"} does not indicate a meta-schema: #{metaschema.pretty_inspect.chomp}")
  end
end

.ensure_schema(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

Yield Returns:

  • (#to_s, #to_ary)

    first line(s) of the error message, overriding the default

Returns:

  • (Schema)

    the given schema

Raises:



297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
# File 'lib/jsi/schema.rb', line 297

def ensure_schema(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.each_yield_set do |is, y|
        is.each_inplace_applicator_schema(schema.jsi_node_content, &y)
      end

      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(
        jsi_document: schema.jsi_document,
        jsi_ptr: schema.jsi_ptr,
        jsi_indicated_schemas: result_schema_indicated_schemas,
        jsi_base_uri: schema.jsi_base_uri,
        jsi_schema_resource_ancestors: schema.jsi_schema_resource_ancestors,
        jsi_schema_dynamic_anchor_map: schema.jsi_schema_dynamic_anchor_map,
        jsi_conf: schema.equal?(schema.jsi_root_node) ? schema.jsi_conf : nil,
        jsi_root_node: schema.equal?(schema.jsi_root_node) ? nil : schema.jsi_root_node, # bad
      ).send(:jsi_initialized)
    else
      msg = []
      msg.concat([*(block_given? ? yield : "indicated object is not a schema:")])
      msg << schema.pretty_inspect.chomp
      if schema.is_a?(Base)
        msg << "its schemas (which should include a Meta-Schema): #{schema.jsi_schemas.pretty_inspect.chomp}"
      end
      raise(NotASchemaError, msg.compact.join("\n"))
    end
  end
end

Instance Method Details

#anchorsEnumerable<String>

Returns:

  • (Enumerable<String>)


404
405
406
407
408
409
# File 'lib/jsi/schema.rb', line 404

def anchors
  anchors = Set[]
  anchors.merge(dialect_invoke_each(:anchor))
  anchors.merge(dialect_invoke_each(:dynamicAnchor))
  anchors.freeze
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)


802
803
804
# File 'lib/jsi/schema.rb', line 802

def described_object_property_names
  @described_object_property_names_map[schema_content: schema_content]
end

#describes_schema!(dialect = nil)

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.

Parameters:

  • dialect (Schema::Dialect, nil) (defaults to: nil)

    dialect may be passed, or inferred from $vocabulary

Raises:

  • (TypeError)


539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
# File 'lib/jsi/schema.rb', line 539

def describes_schema!(dialect = nil)
  # TODO rm bridge code hax
  dialect = dialect.first::DIALECT if dialect.is_a?(Array) && dialect.size == 1

  if !dialect
    raise(ArgumentError, "no dialect given and no $vocabulary hash/object") if !schema_content['$vocabulary'].respond_to?(:to_hash)
    dialect = Schema::Dialect.from_xvocabulary(schema_content['$vocabulary'], registry: jsi_registry)
  end

  raise(TypeError) if !dialect.is_a?(Schema::Dialect)

  if jsi_schema_module <= Schema
    # this schema 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 dialect, though.
    if @described_dialect != dialect
      raise(ArgumentError, "this schema already describes a schema with different dialect")
    end
  else
    jsi_schema_module.include(Schema)
    jsi_schema_module.send(:define_method, :dialect) { dialect }
    proc { |metaschema| jsi_schema_module.send(:define_method, :metaschema) { metaschema } }[self]
    jsi_schema_module.extend(SchemaModule::MetaSchemaModule)
  end

  @described_dialect = dialect
  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)


522
523
524
# File 'lib/jsi/schema.rb', line 522

def describes_schema?
  is_a?(Schema::MetaSchema)
end

#dialectSchema::Dialect

The dialect of this schema

Returns:



# File 'lib/jsi/schema.rb', line 371

#dialect_invoke_each(action_name, cxt_class = Cxt::Block, **cxt_param) { ... } ⇒ Object

Parameters:

  • action_name (Symbol)
  • cxt_class (Class) (defaults to: Cxt::Block)

Yields:



884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
# File 'lib/jsi/schema.rb', line 884

def dialect_invoke_each(
    action_name,
    cxt_class = Cxt::Block,
    **cxt_param,
    &block
)
  return(to_enum(__method__, action_name, cxt_class, **cxt_param)) unless block_given?

  cxt = cxt_class.new(
    schema: self,
    abort: false,
    block: block,
    **cxt_param,
  )
  dialect.invoke(action_name, cxt)

  nil
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



718
719
720
721
722
723
724
725
726
727
728
# File 'lib/jsi/schema.rb', line 718

def each_child_applicator_schema(token, instance, &block)
  dialect_invoke_each(:child_applicate,
    Cxt::ChildApplication,
    instance: instance,
    token: token,
    collect_evaluated: false,
    collect_evaluated_validate: false,
    evaluated: false,
    &block
  )
end

#each_immediate_subschema_ptr {|Ptr| ... } ⇒ Object

Yields:



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

def each_immediate_subschema_ptr
  return(to_enum(__method__)) unless block_given?

  dialect_invoke_each(:subschema) { |ptr| yield(Ptr.ary_ptr(ptr)) }
end

#each_inplace_applicator_schema(instance, visited_refs: Util::EMPTY_ARY) {|JSI::Schema| ... } ⇒ nil

Yields each in-place applicator schema which applies to the given instance.

Parameters:

  • instance (Object)

    the instance to check any applicators against

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

Yields:

Returns:

  • (nil)


689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
# File 'lib/jsi/schema.rb', line 689

def each_inplace_applicator_schema(
    instance,
    visited_refs: Util::EMPTY_ARY,
    &block
)
  each_immediate_inplace_applicator_schema(
    instance: instance,
    visited_refs: visited_refs,
    collect_evaluated: false, # child application is not invoked so no evaluated children to collect
  ) do |schema, ref: nil, applicate: true|
    if schema.equal?(self) && !ref
      yield(self)
    elsif applicate
      schema.each_inplace_applicator_schema(
        instance,
        visited_refs: Util.add_visited_ref(visited_refs, ref),
        &block
      )
    end
  end
end

#each_inplace_child_applicator_schema(token, instance, visited_refs: Util::EMPTY_ARY, collect_evaluated: false, collect_evaluated_validate: false) {|Schema| ... } ⇒ Boolean

For each in-place applicator schema that applies to the given instance, yields each child applicator of that schema that applies to the child of the instance on the given token.

This method handles collection of whether the child was evaluated by any applicator when that evaluation is needed by either this schema or the caller (per param collect_evaluated). This is relevant to schemas containing unevaluatedProperties or unevaluatedItems.

Parameters:

  • token (Object)

    array index or hash/object property name

  • instance (Object)
  • collect_evaluated (Boolean) (defaults to: false)

    Does the caller need this method to collect successful child evaluation? Note: this method will still collect child evaluation if this schema needs it; this only needs to be passed true when called by an in-place applicator schema that needs it (i.e. contains unevaluated*).

  • collect_evaluated_validate (Boolean) (defaults to: false)

Yields:

Returns:

  • (Boolean)

    if collect_evaluated is true, whether the child was successfully evaluated by a child applicator schema. if collect_evaluated is false, undefined/void.



746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
# File 'lib/jsi/schema.rb', line 746

def each_inplace_child_applicator_schema(
    token,
    instance,
    visited_refs: Util::EMPTY_ARY,
    collect_evaluated: false,
    collect_evaluated_validate: false,
    &block
)
  collect_evaluated ||= application_requires_evaluated
  inplace_child_evaluated = false
  applicate_self = false

  each_immediate_inplace_applicator_schema(
    instance: instance,
    visited_refs: visited_refs,
    collect_evaluated: collect_evaluated,
  ) do |schema, ref: nil, applicate: true|
    if schema.equal?(self) && !ref
      applicate_self = true
    elsif applicate || (collect_evaluated && !inplace_child_evaluated)
      schema_evaluated = schema.each_inplace_child_applicator_schema(
        token,
        instance,
        visited_refs: Util.add_visited_ref(visited_refs, ref),
        collect_evaluated: collect_evaluated && !inplace_child_evaluated,
        collect_evaluated_validate: collect_evaluated_validate,
        # the `if` keyword needs to yield to here because it does affect `evaluated`,
        # but it does not applicate itself/its applicators, so does not yield to the given block.
        &(applicate ? block : proc { })
      )
      inplace_child_evaluated ||= collect_evaluated && schema_evaluated && (!collect_evaluated_validate || schema.instance_valid?(instance))
    end
  end

  if applicate_self
    child_application = dialect.invoke(:child_applicate, Cxt::ChildApplication.new(
      schema: self,
      abort: false,
      token: token,
      instance: instance,
      collect_evaluated: collect_evaluated,
      collect_evaluated_validate: collect_evaluated_validate,
      evaluated: inplace_child_evaluated,
      block: block,
    ))

    child_application.evaluated
  else
    inplace_child_evaluated
  end
end

#id#to_str?

the string contents of an $id/id keyword, or nil

Returns:

  • (#to_str, nil)


399
400
401
# File 'lib/jsi/schema.rb', line 399

def id
  dialect_invoke_each(:id).first
end

#initializeObject



360
361
362
363
# File 'lib/jsi/schema.rb', line 360

def initialize(*)
  super
  jsi_schema_initialize
end

#instance_valid!(instance) ⇒ nil

Asserts that the given instance is valid against this schema. Error::Invalid is raised if it is not.

Returns:

  • (nil)

Raises:



836
837
838
# File 'lib/jsi/schema.rb', line 836

def instance_valid!(instance)
  instance_validate(instance).valid!
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)


824
825
826
827
828
829
# File 'lib/jsi/schema.rb', line 824

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::Full

Validates the given instance against this schema, returning a result with each validation error.

Parameters:

  • instance (Object)

    the instance to validate against this schema

Returns:



810
811
812
813
814
815
816
817
818
819
# File 'lib/jsi/schema.rb', line 810

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_as_child_default_as_jsiObject

See Base#jsi_as_child_default_as_jsi. true for Schema, including boolean schemas.



877
878
879
# File 'lib/jsi/schema.rb', line 877

def jsi_as_child_default_as_jsi
  true
end

#jsi_each_descendent_schema {|Schema| ... } ⇒ Object

Yields:



615
616
617
618
619
620
# File 'lib/jsi/schema.rb', line 615

def jsi_each_descendent_schema(&block)
  return(to_enum(__method__)) unless block_given?

  yield(self)
  dialect_invoke_each(:subschema) { |ptr| subschema(ptr).jsi_each_descendent_schema(&block) }
end

#jsi_each_descendent_schema_same_resource {|Schema| ... } ⇒ Object

yields each descendent of this node (including itself) within the same resource that is a Schema

Yields:



624
625
626
627
628
629
630
631
632
633
634
# File 'lib/jsi/schema.rb', line 624

def jsi_each_descendent_schema_same_resource(&block)
  return(to_enum(__method__)) unless block_given?

  yield(self)
  dialect_invoke_each(:subschema) do |ptr|
    desc = subschema(ptr)
    if !desc.jsi_is_resource_root?
      desc.jsi_each_descendent_schema_same_resource(&block)
    end
  end
end

#jsi_is_resource_root?Boolean

is this schema the root of a schema resource?

Returns:

  • (Boolean)


586
587
588
# File 'lib/jsi/schema.rb', line 586

def jsi_is_resource_root?
  super || jsi_resource_uris.any?
end

#jsi_is_schema?Boolean

Is this a JSI Schema?

Returns:

  • (Boolean)


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

def jsi_is_schema?
  true
end

#jsi_schema_moduleSchemaModule

The JSI Schema Module for this schema. JSI instances described by this schema are instances of this module.

Returns:



476
477
478
# File 'lib/jsi/schema.rb', line 476

def jsi_schema_module
  jsi_schema_module_connection
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



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

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

#jsi_schema_module_nameString?

Returns:

  • (String, nil)


491
492
493
494
# File 'lib/jsi/schema.rb', line 491

def jsi_schema_module_name
  # don't hit #jsi_schema_module - avoid creating module, avoid erroring for MSN::BootstrapSchema
  @memos[:schema_module_connection] && @memos[:schema_module_connection].name
end

#jsi_schema_module_name_from_ancestorString?

Returns:

  • (String, nil)


497
498
499
# File 'lib/jsi/schema.rb', line 497

def jsi_schema_module_name_from_ancestor
  is_a?(Base) ? jsi_schema_module.name_from_ancestor : nil
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:



907
908
909
910
911
912
913
# File 'lib/jsi/schema.rb', line 907

def jsi_subschema_resource_ancestors
  if jsi_is_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)


386
387
388
389
# File 'lib/jsi/schema.rb', line 386

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

#keyword_value?(keyword, value) ⇒ Boolean

Does this schema contain the given keyword with the given value?

Returns:

  • (Boolean)


393
394
395
# File 'lib/jsi/schema.rb', line 393

def keyword_value?(keyword, value)
  keyword?(keyword) && schema_content[keyword] == value
end

#new_jsi(instance, **kw) ⇒ Base

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

All parameters are passed to JSI::SchemaSet#new_jsi.

Returns:

  • (Base)

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

Raises:



509
510
511
512
# File 'lib/jsi/schema.rb', line 509

def new_jsi(instance, **kw)
  raise(BlockGivenError) if block_given?
  SchemaSet[self].new_jsi(instance, **kw)
end

#resource_root_subschema(ptr) ⇒ JSI::Schema

A schema in the same schema resource as this one (see JSI::Schema::SchemaAncestorNode#jsi_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:



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

def resource_root_subschema(ptr)
      Schema.ensure_schema(jsi_resource_root.jsi_descendent_node(ptr),
        reinstantiate_as: jsi_conf.reinstantiate_nonschemas && jsi_schemas.select(&:describes_schema?),
      )
end

#schema_absolute_uriURI?

Deprecated.

after v0.8 - use #jsi_resource_uri

the URI of this schema, from an $id keyword, resolved against our #jsi_base_uri

Returns:

  • (URI, nil)


414
415
416
# File 'lib/jsi/schema.rb', line 414

def schema_absolute_uri
  jsi_resource_uri
end

#schema_absolute_urisEnumerable<URI>

Deprecated.

after v0.8 - use #jsi_resource_uris

Returns:

  • (Enumerable<URI>)


420
421
422
# File 'lib/jsi/schema.rb', line 420

def schema_absolute_uris
  jsi_resource_uris
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.



380
381
382
# File 'lib/jsi/schema.rb', line 380

def schema_content
  jsi_node_content
end

#schema_ref(ref = ) ⇒ Schema::Ref

Parameters:

  • ref (#to_str) (defaults to: )

    ref URI

Returns:



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

def schema_ref(ref = schema_content["$ref"])
  @schema_ref_map[ref]
end

#schema_resource_rootJSI::Base

Deprecated.

after v0.8

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



580
581
582
# File 'lib/jsi/schema.rb', line 580

def schema_resource_root
  jsi_resource_root
end

#schema_resource_root?Boolean

Deprecated.

after v0.8

Returns:

  • (Boolean)


591
592
593
# File 'lib/jsi/schema.rb', line 591

def schema_resource_root?
  jsi_is_resource_root?
end

#schema_uriURI?

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:

  • (URI, nil)


440
441
442
# File 'lib/jsi/schema.rb', line 440

def schema_uri
  schema_uris.first
end

#schema_urisArray<URI>

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

Returns:

  • (Array<URI>)


446
447
448
# File 'lib/jsi/schema.rb', line 446

def schema_uris
  @schema_uris_map[schema_content: schema_content]
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.



599
600
601
# File 'lib/jsi/schema.rb', line 599

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