# frozen_string_literal: true module JSI # JSI::Schema is a module which extends {JSI::Base} instances which represent JSON schemas. # # This module is included on the {Schema#jsi_schema_module JSI Schema module} of any schema # which describes other schemas, i.e. is a metaschema or other {Schema::DescribesSchema}. # Therefore, any JSI instance described by a schema which is a {Schema::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. module Schema autoload :Application, 'jsi/schema/application' autoload :Validation, 'jsi/schema/validation' autoload :Issue, 'jsi/schema/issue' autoload :Ref, 'jsi/schema/ref' autoload :SchemaAncestorNode, 'jsi/schema/schema_ancestor_node' autoload :Draft04, 'jsi/schema/draft04' autoload :Draft06, 'jsi/schema/draft06' autoload :Draft07, 'jsi/schema/draft07' class Error < StandardError end # an exception raised when a thing is expected to be a JSI::Schema, but is not class NotASchemaError < Error end # an exception raised when we are unable to resolve a schema reference class ReferenceError < StandardError end # extends any schema which uses the keyword '$id' to identify its canonical URI module BigMoneyId # the contents of a $id keyword whose value is a string, or nil # @return [#to_str, nil] def id if keyword?('$id') && schema_content['$id'].respond_to?(:to_str) schema_content['$id'] else nil end end end # extends any schema which uses the keyword 'id' to identify its canonical URI module OldId # the contents of an `id` keyword whose value is a string, or nil # @return [#to_str, nil] def id if keyword?('id') && schema_content['id'].respond_to?(:to_str) schema_content['id'] else nil end end end # extends any schema which defines an anchor as a URI fragment in the schema id module IdWithAnchor # a URI for the schema's id, unless the id defines an anchor in its # fragment. nil if the schema defines no id. # @return [Addressable::URI, nil] def id_without_fragment if id id_uri = Util.uri(id) if id_uri.merge(fragment: nil).empty? # fragment-only id is just an anchor # e.g. #foo nil elsif id_uri.fragment == nil # no fragment # e.g. http://localhost:1234/bar id_uri elsif id_uri.fragment == '' # empty fragment # e.g. http://json-schema.org/draft-07/schema# id_uri.merge(fragment: nil).freeze elsif jsi_schema_base_uri && jsi_schema_base_uri.join(id_uri).merge(fragment: nil) == jsi_schema_base_uri # the id, resolved against the base uri, consists of the base uri plus an anchor fragment. # so there's no non-fragment id. # e.g. base uri is http://localhost:1234/bar # and id is http://localhost:1234/bar#foo nil else # e.g. http://localhost:1234/bar#foo id_uri.merge(fragment: nil).freeze end else nil end end # an anchor defined by a non-empty fragment in the id uri # @return [String] def anchor if id id_uri = Util.uri(id) if id_uri.fragment == '' nil else id_uri.fragment end else nil end end end # @private module IntegerAllows0Fraction # is `value` an integer? # @private # @param value # @return [Boolean] def internal_integer?(value) value.is_a?(Integer) || (value.is_a?(Numeric) && value % 1.0 == 0.0) end end # @private module IntegerDisallows0Fraction # is `value` an integer? # @private # @param value # @return [Boolean] def internal_integer?(value) value.is_a?(Integer) end end # This module extends any JSI Schema which describes schemas. # # Examples of a schema which describes schemas include the JSON Schema metaschemas and # the OpenAPI schema definition which describes "A deterministic version of a JSON Schema object." # # Schemas which describes schemas include {JSI::Schema} in their # {Schema#jsi_schema_module JSI Schema module}, so for a schema which is an instance of # DescribesSchema, instances of that schema are instances of {JSI::Schema} and are schemas. # # A schema is indicated as describing other schemas using the {Schema#describes_schema!} method. module DescribesSchema # Instantiates the given schema content as a JSI Schema. # # By default, the schema will be registered with the {JSI.schema_registry}. # This can be controlled by params `register` and `schema_registry`. # # By default, the `schema_content` will have any Symbol keys of Hashes replaced with Strings # (recursively through the document). This is controlled by the param `stringify_symbol_keys`. # # @param schema_content an object to be instantiated as a JSI Schema - typically a Hash # @param uri [#to_str, Addressable::URI] The retrieval URI of the schema document. # If specified, the root schema will be identified by this URI, in addition # to any absolute URI declared with an id keyword, for resolution in the `schema_registry`. # # It is rare that this needs to be specified. Most schemas, if they use absolute URIs, will # use the `$id` keyword (`id` in draft 4) to specify this. A different retrieval URI is useful # in unusual cases: # # - A schema in the document uses relative URIs for `$id` or `$ref` without an absolute id in an # ancestor schema - these will be resolved relative to this URI # - Another schema refers with `$ref` to the schema being instantiated by this retrieval URI, # rather than an id declared in the schema - the schema is resolvable by this URI in the # `schema_registry`. # @param register [Boolean] Whether the instantiated schema and any subschemas with absolute URIs # will be registered in the schema registry indicated by param `schema_registry`. # @param schema_registry [SchemaRegistry, nil] The registry this schema will use. # # - The schema and subschemas will be registered here with any declared URI, # unless the `register` param is false. # - References from within the schema (typically from `$ref` keywords) are resolved using this registry. # @param stringify_symbol_keys [Boolean] Whether the schema content will have any Symbol keys of Hashes # replaced with Strings (recursively through the document). # Replacement is done on a copy; the given schema content is not modified. # @yield If a block is given, it is evaluated in the context of the schema's JSI schema module # using [Module#module_exec](https://ruby-doc.org/core/Module.html#method-i-module_exec). # @return [JSI::Base subclass + JSI::Schema] a JSI which is a {JSI::Schema} whose content comes from # the given `schema_content` and whose schemas are this schema's inplace applicators. def new_schema(schema_content, uri: nil, register: true, schema_registry: JSI.schema_registry, stringify_symbol_keys: true, &block ) schema_jsi = new_jsi(schema_content, uri: uri, register: register, schema_registry: schema_registry, stringify_symbol_keys: stringify_symbol_keys, ) if block schema_jsi.jsi_schema_module_exec(&block) end schema_jsi end # Instantiates the given schema content as a JSI Schema, passing all params to # {Schema::DescribesSchema#new_schema}, and returns its {Schema#jsi_schema_module JSI Schema Module}. # # @return [Module + JSI::SchemaModule] the JSI Schema Module of the instantiated schema def new_schema_module(schema_content, **kw, &block) new_schema(schema_content, **kw, &block).jsi_schema_module end end class << self def extended(o) super o.send(:jsi_schema_initialize) end end end class << self # An application-wide default metaschema set by {default_metaschema=}, used by {JSI.new_schema} # to instantiate schemas which do not specify their metaschema using a `$schema` property. # # @return [nil, Base + Schema + Schema::DescribesSchema] def @default_metaschema end # Sets {default_metaschema} to a schema indicated by the given param. # # @param default_metaschema [Schema::DescribesSchema, SchemaModule::DescribesSchemaModule, #to_str, nil] # Indicates the default metaschema. # This may be a metaschema or a metaschema's schema module (e.g. `JSI::JSONSchemaDraft07`), # or a URI (as would be in a `$schema` keyword). # # `nil` to unset. def () @default_metaschema = .nil? ? nil : ensure_describes_schema() end # Instantiates the given schema content as a JSI Schema. # # The metaschema which describes the schema must be indicated: # # - If the schema object has a `$schema` property, that URI is resolved using the `schema_registry` # param (by default {JSI.schema_registry}), and that metaschema is used. For example: # # ```ruby # JSI.new_schema({ # "$schema" => "http://json-schema.org/draft-07/schema#", # "properties" => ..., # }) # ``` # # - if no `$schema` property is present, the `default_metaschema` param is used, if the caller # specifies it. For example: # # ```ruby # JSI.new_schema({"properties" => ...}, default_metaschema: JSI::JSONSchemaDraft07) # ``` # # - if no `default_metaschema` param is specified, the application-wide default # {JSI.default_metaschema JSI.default_metaschema} is used, # if the application has set it. For example: # # ```ruby # JSI.default_metaschema = JSI::JSONSchemaDraft07 # JSI.new_schema({"properties" => ...}) # ``` # # an ArgumentError is raised if none of these indicate a metaschema to use. # # Note that if you are instantiating a schema known to have no `$schema` property, an alternative to # specifying a `default_metaschema` is to call `new_schema` on the # {Schema::DescribesSchema#new_schema metaschema} or its # {SchemaModule::DescribesSchemaModule#new_schema schema module}, e.g. # `JSI::JSONSchemaDraft07.new_schema(my_schema_content)` # # @param schema_content (see Schema::DescribesSchema#new_schema) # @param default_metaschema [Schema::DescribesSchema, SchemaModule::DescribesSchemaModule, #to_str] # Indicates the metaschema to use if the given schema_content does not have a `$schema` property. # This may be a metaschema or a metaschema's schema module (e.g. `JSI::JSONSchemaDraft07`), # or a URI (as would be in a `$schema` keyword). # @param uri (see Schema::DescribesSchema#new_schema) # @param register (see DescribesSchema#new_schema) # @param schema_registry (see DescribesSchema#new_schema) # @param stringify_symbol_keys (see Schema::DescribesSchema#new_schema) # @yield (see Schema::DescribesSchema#new_schema) # @return [JSI::Base subclass + JSI::Schema] a JSI which is a {JSI::Schema} whose content comes from # the given `schema_content` and whose schemas are inplace applicators of the indicated metaschema def new_schema(schema_content, default_metaschema: nil, # params of DescribesSchema#new_schema have their default values repeated here. delegating in a splat # would remove repetition, but yard doesn't display delegated defaults with its (see X) directive. uri: nil, register: true, schema_registry: JSI.schema_registry, stringify_symbol_keys: true, &block ) new_schema_params = { uri: uri, register: register, schema_registry: schema_registry, stringify_symbol_keys: stringify_symbol_keys, } = -> { = if Schema.ensure_describes_schema(, name: "default_metaschema") elsif self. self. else raise(ArgumentError, [ "When instantiating a schema with no `$schema` property, you must specify its metaschema by one of these methods:", "- pass the `default_metaschema` param to this method", " e.g.: JSI.new_schema(..., default_metaschema: JSI::JSONSchemaDraft07)", "- invoke `new_schema` on the appropriate metaschema or its schema module", " e.g.: JSI::JSONSchemaDraft07.new_schema(...)", "- set JSI.default_metaschema to an application-wide default metaschema initially", " e.g.: JSI.default_metaschema = JSI::JSONSchemaDraft07", "instantiating schema_content: #{schema_content.pretty_inspect.chomp}", ].join("\n")) end .new_schema(schema_content, **new_schema_params, &block) } if schema_content.is_a?(Schema) raise(TypeError, [ "Given schema_content is already a JSI::Schema. It cannot be instantiated as the content of a schema.", "given: #{schema_content.pretty_inspect.chomp}", ].join("\n")) elsif schema_content.is_a?(JSI::Base) raise(TypeError, [ "Given schema_content is a JSI::Base. It cannot be instantiated as the content of a schema.", "given: #{schema_content.pretty_inspect.chomp}", ].join("\n")) elsif schema_content.respond_to?(:to_hash) id = schema_content['$schema'] || stringify_symbol_keys && schema_content[:'$schema'] if id unless id.respond_to?(:to_str) raise(ArgumentError, "given schema_content keyword `$schema` is not a string") end = Schema.ensure_describes_schema(id, name: '$schema', schema_registry: schema_registry) .new_schema(schema_content, **new_schema_params, &block) else .call end else .call end end end self. = nil module Schema class << self # ensure the given object is a JSI Schema # # @param schema [Object] the thing the caller wishes to ensure is a Schema # @param msg [#to_s, #to_ary] lines of the error message preceding the pretty-printed schema param # if the schema param is not a schema # @raise [NotASchemaError] if the schema param is not a schema # @return [Schema] the given schema 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 # Ensures the given param identifies a JSI Schema which describes schemas, and returns that schema. # # @api private # @param describes_schema [Schema::DescribesSchema, SchemaModule::DescribesSchemaModule, #to_str] # @raise [TypeError] if the param does not indicate a schema which describes schemas # @return [Base + Schema + Schema::DescribesSchema] 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 end if Util::LAST_ARGUMENT_AS_KEYWORD_PARAMETERS def initialize(*) super jsi_schema_initialize end else def initialize(*, **) super jsi_schema_initialize end end # 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. def schema_content jsi_node_content end # does this schema contain the given keyword? # @return [Boolean] def keyword?(keyword) schema_content = jsi_node_content schema_content.respond_to?(:to_hash) && schema_content.key?(keyword) end # the URI of this schema, calculated from our `#id`, resolved against our `#jsi_schema_base_uri` # @return [Addressable::URI, nil] 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 # 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. # @return [Addressable::URI, nil] def schema_uri schema_uris.first end # nonrelative URIs (that is, absolute, but possibly with a fragment) which refer to this schema # @return [Array<Addressable::URI>] def schema_uris @schema_uris_map[] end private def schema_uris_compute(**_) # TODO remove **_ eventually (keyword argument compatibility) each_schema_uri.to_a end # see {#schema_uris} # @yield [Addressable::URI] # @return [Enumerator, nil] 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 # 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. # # @return [Module + SchemaModule] def jsi_schema_module JSI::SchemaClasses.module_for_schema(self) end # 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](https://ruby-doc.org/core/Module.html#method-i-module_exec) # on our {#jsi_schema_module}. # # @return the result of evaluating the block def jsi_schema_module_exec(*a, **kw, &block) jsi_schema_module.module_exec(*a, **kw, &block) end # 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. # # @param (see SchemaSet#new_jsi) # @return [JSI::Base subclass] a JSI whose content comes from the given instance and whose schemas are # inplace applicators of this schema. def new_jsi(instance, **kw) SchemaSet[self].new_jsi(instance, **kw) end # @param keyword schema keyword e.g. "$ref", "$schema" # @return [Schema::Ref] def schema_ref(keyword: "$ref") raise(ArgumentError, "keyword not present: #{keyword}") unless keyword?(keyword) @schema_ref_map[keyword: keyword, value: schema_content[keyword]] end # does this schema itself describe a schema? # @return [Boolean] def describes_schema? jsi_schema_module <= JSI::Schema || false end # indicates that this schema describes a schema. # this schema is extended with {DescribesSchema} and its {#jsi_schema_module} is extended # with {SchemaModule::DescribesSchemaModule}, and the JSI Schema Module will include # JSI::Schema and the given modules. # # @param schema_implementation_modules [Enumerable<Module>] modules which implement the functionality of # the schema to extend schemas described by this schema. # @return [void] 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 # 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 {Base#jsi_root_node document's root node}. # In this case, the resource root may or may not be a schema itself. # # @return [JSI::Base] resource containing this schema def schema_resource_root jsi_subschema_resource_ancestors.last || jsi_root_node end # is this schema the root of a schema resource? # @return [Boolean] def schema_resource_root? jsi_ptr.root? || !!schema_absolute_uri end # a subschema of this Schema # # @param subptr [JSI::Ptr, #to_ary] a relative pointer, or array of tokens, pointing to the subschema # @return [JSI::Schema] the subschema at the location indicated by subptr. self if subptr is empty. def subschema(subptr) @subschema_map[subptr: Ptr.ary_ptr(subptr)] end private def subschema_compute(subptr: ) Schema.ensure_schema(jsi_descendent_node(subptr), msg: [ "subschema is not a schema at pointer: #{subptr.pointer}" ]) end # 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. # # @param ptr [JSI::Ptr, #to_ary] a pointer to a schema from our schema resource root # @return [JSI::Schema] the schema pointed to by ptr def resource_root_subschema(ptr) @resource_root_subschema_map[ptr: Ptr.ary_ptr(ptr)] end private def resource_root_subschema_compute(ptr: ) Schema.ensure_schema(schema_resource_root.jsi_descendent_node(ptr), msg: [ "subschema is not a schema at pointer: #{ptr.pointer}" ], reinstantiate_as: jsi_schemas.select(&:describes_schema?) ) end # 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. # # @param instance [Object] the instance to check any applicators against # @return [JSI::SchemaSet] matched applicator schemas def inplace_applicator_schemas(instance) SchemaSet.new(each_inplace_applicator_schema(instance)) end # yields each inplace applicator schema which applies to the given instance. # # @param instance (see #inplace_applicator_schemas) # @param visited_refs [Enumerable<JSI::Schema::Ref>] # @yield [JSI::Schema] # @return [nil, Enumerator] an Enumerator if invoked without a block; otherwise nil 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 # a set of child applicator subschemas of this schema which apply to the child of the given instance # on the given token. # # @param token [Object] the array index or object property name for the child instance # @param instance [Object] the instance to check any child applicators against # @return [JSI::SchemaSet] child applicator subschemas of this schema for the given token # of the instance def child_applicator_schemas(token, instance) SchemaSet.new(each_child_applicator_schema(token, instance)) end # yields each child applicator subschema (from properties, items, etc.) which applies to the child of # the given instance on the given token. # # @param (see #child_applicator_schemas) # @yield [JSI::Schema] # @return [nil, Enumerator] an Enumerator if invoked without a block; otherwise nil 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 # 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. # @return [Set] def described_object_property_names @described_object_property_names_map[] end private def described_object_property_names_compute(**_) # TODO remove **_ eventually (keyword argument compatibility) Set.new.tap do |property_names| if schema_content.respond_to?(:to_hash) && schema_content['properties'].respond_to?(:to_hash) property_names.merge(schema_content['properties'].keys) end if schema_content.respond_to?(:to_hash) && schema_content['required'].respond_to?(:to_ary) property_names.merge(schema_content['required'].to_ary) end end.freeze end # validates the given instance against this schema # # @param instance [Object] the instance to validate against this schema # @return [JSI::Validation::Result] 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 # whether the given instance is valid against this schema # @param instance [Object] the instance to validate against this schema # @return [Boolean] 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 # validates the given instance against this schema # # @private # @param instance_ptr [JSI::Ptr] a pointer to the instance to validate against the schema, in the instance_document # @param instance_document [#to_hash, #to_ary, Object] document containing the instance instance_ptr pointer points to # @param validate_only [Boolean] whether to return a full schema validation result or a simple, validation-only result # @param visited_refs [Enumerable<JSI::Schema::Ref>] # @return [JSI::Validation::Result] def internal_validate_instance(instance_ptr, instance_document, validate_only: false, visited_refs: []) if validate_only result = JSI::Validation::VALID else result = JSI::Validation::FullResult.new end result_builder = result.builder(self, instance_ptr, instance_document, validate_only, visited_refs) catch(:jsi_validation_result) do # note: true/false are not valid as schemas in draft 4; they are only values of # additionalProperties / additionalItems. since their behavior is undefined, though, # it's fine for them to behave the same as boolean schemas in later drafts. # I don't care about draft 4 to implement a different structuring for that. if schema_content == true # noop elsif schema_content == false result_builder.validate(false, 'instance is not valid against `false` schema') elsif schema_content.respond_to?(:to_hash) internal_validate_keywords(result_builder) else result_builder.schema_error('schema is not a boolean or a JSON object') end result end.freeze end # schema resources which are ancestors of any subschemas below this schema. # this may include this schema if this is a schema resource root. # @api private # @return [Array<JSI::Schema>] def jsi_subschema_resource_ancestors if schema_resource_root? jsi_schema_resource_ancestors.dup.push(self).freeze else jsi_schema_resource_ancestors end end private def jsi_schema_initialize @schema_ref_map = jsi_memomap(key_by: proc { |i| i[:keyword] }) do |keyword: , value: | Schema::Ref.new(value, ref_schema: self) end @schema_uris_map = jsi_memomap(&method(:schema_uris_compute)) @subschema_map = jsi_memomap(&method(:subschema_compute)) @resource_root_subschema_map = jsi_memomap(&method(:resource_root_subschema_compute)) @described_object_property_names_map = jsi_memomap(&method(:described_object_property_names_compute)) end end end