Module: JSI::Schema

Defined in:
lib/jsi/schema.rb

Overview

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

the content of an instance which is a JSI::Schema (referred to in this context as schema_content) is expected to be a Hash (JSON object) or a Boolean.

Defined Under Namespace

Modules: DescribesSchema Classes: Error, NotASchemaError

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.default_metaschemaJSI::Schema

Returns the default metaschema.

Returns:



27
28
29
# File 'lib/jsi/schema.rb', line 27

def default_metaschema
  JSI::JSONSchemaOrgDraft06.schema
end

.from_object(schema_object) ⇒ JSI::Schema Also known as: new

instantiates a given schema object as a JSI::Schema.

schemas are instantiated according to their '$schema' property if specified. otherwise their schema will be the default_metaschema.

if the given schema_object is a JSI::Base but not already a JSI::Schema, an error will be raised. JSI::Base should already extend a given instance with JSI::Schema when its schema describes a schema (by extending with JSI::Schema::DescribesSchema).

Parameters:

  • schema_object (#to_hash, Boolean, JSI::Schema)

    an object to be instantiated as a schema. if it's already a schema, it is returned as-is.

Returns:

  • (JSI::Schema)

    a JSI::Schema representing the given schema_object



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/jsi/schema.rb', line 51

def from_object(schema_object)
  if schema_object.is_a?(Schema)
    schema_object
  elsif schema_object.is_a?(JSI::Base)
    raise(NotASchemaError, "the given schema_object is a JSI::Base, but is not a JSI::Schema: #{schema_object.pretty_inspect.chomp}")
  elsif schema_object.respond_to?(:to_hash)
    schema_object = JSI.deep_stringify_symbol_keys(schema_object)
    if schema_object.key?('$schema') && schema_object['$schema'].respond_to?(:to_str)
      if schema_object['$schema'] == schema_object['$id'] || schema_object['$schema'] == schema_object['id']
        MetaschemaNode.new(schema_object)
      else
        metaschema = supported_metaschemas.detect { |ms| schema_object['$schema'] == ms['$id'] || schema_object['$schema'] == ms['id'] }
        unless metaschema
          raise(NotImplementedError, "metaschema not supported: #{schema_object['$schema']}")
        end
        metaschema.new_jsi(schema_object)
      end
    else
      default_metaschema.new_jsi(schema_object)
    end
  elsif [true, false].include?(schema_object)
    default_metaschema.new_jsi(schema_object)
  else
    raise(TypeError, "cannot instantiate Schema from: #{schema_object.pretty_inspect.chomp}")
  end
end

.supported_metaschemasArray<JSI::Schema>

Returns supported metaschemas.

Returns:



32
33
34
35
36
37
# File 'lib/jsi/schema.rb', line 32

def supported_metaschemas
  [
    JSI::JSONSchemaOrgDraft04.schema,
    JSI::JSONSchemaOrgDraft06.schema,
  ]
end

Instance Method Details

#described_object_property_namesSet

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

    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.



194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/jsi/schema.rb', line 194

def described_object_property_names
  jsi_memoize(:described_object_property_names) do
    Set.new.tap do |property_names|
      if node_content.respond_to?(:to_hash) && node_content['properties'].respond_to?(:to_hash)
        property_names.merge(node_content['properties'].keys)
      end
      if node_content.respond_to?(:to_hash) && node_content['required'].respond_to?(:to_ary)
        property_names.merge(node_content['required'].to_ary)
      end
    end
  end
end

#describes_schema?Boolean

Returns does this schema itself describe a schema?.

Returns:

  • (Boolean)

    does this schema itself describe a schema?



148
149
150
# File 'lib/jsi/schema.rb', line 148

def describes_schema?
  is_a?(JSI::Schema::DescribesSchema)
end

#fully_validate_instance(other_instance, errors_as_objects: false) ⇒ Array

Returns array of schema validation errors for the given instance against this schema.

Returns:

  • (Array)

    array of schema validation errors for the given instance against this schema



209
210
211
# File 'lib/jsi/schema.rb', line 209

def fully_validate_instance(other_instance, errors_as_objects: false)
  ::JSON::Validator.fully_validate(JSI::Typelike.as_json(node_document), JSI::Typelike.as_json(other_instance), fragment: node_ptr.fragment, errors_as_objects: errors_as_objects)
end

#fully_validate_schema(errors_as_objects: false) ⇒ Array

Returns array of schema validation errors for this schema, validated against its metaschema. a default metaschema is assumed if the schema does not specify a $schema.

Returns:

  • (Array)

    array of schema validation errors for this schema, validated against its metaschema. a default metaschema is assumed if the schema does not specify a $schema.



229
230
231
# File 'lib/jsi/schema.rb', line 229

def fully_validate_schema(errors_as_objects: false)
  ::JSON::Validator.fully_validate(JSI::Typelike.as_json(node_document), [], fragment: node_ptr.fragment, validate_schema: true, list: true, errors_as_objects: errors_as_objects)
end

#jsi_schema_classClass < JSI::Base] a JSI class for this one schema

Returns Class < JSI::Base] a JSI class for this one schema.

Returns:

  • (Class < JSI::Base] a JSI class for this one schema)

    Class < JSI::Base] a JSI class for this one schema



132
133
134
# File 'lib/jsi/schema.rb', line 132

def jsi_schema_class
  JSI.class_for_schemas(Set[self])
end

#jsi_schema_moduleModule

Returns a module representing this schema. see JSI::SchemaClasses.module_for_schema.

Returns:



127
128
129
# File 'lib/jsi/schema.rb', line 127

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

#match_to_instance(other_instance) ⇒ Set<JSI::Schema>

checks this schema for applicators ($ref, allOf, etc.) which should be applied to the given instance. returns these as a Set of JSI::Schemas.

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

Parameters:

  • other_instance (Object)

    the instance to check any applicators against

Returns:



159
160
161
162
163
# File 'lib/jsi/schema.rb', line 159

def match_to_instance(other_instance)
  node_ptr.schema_match_ptrs_to_instance(node_document, other_instance).map do |ptr|
    ptr.evaluate(document_root_node).tap { |subschema| jsi_ensure_subschema_is_schema(subschema, ptr) }
  end.to_set
end

#new_jsi(other_instance, *a, &b) ⇒ JSI::Base

instantiates the given other_instance as a JSI::Base class for schemas matched from this schema to the other_instance.

any parameters are passed to JSI::Base#initialize, but none are normally used.

Returns:

  • (JSI::Base)

    a JSI whose instance is the given instance and whose schemas are matched from this schema.



143
144
145
# File 'lib/jsi/schema.rb', line 143

def new_jsi(other_instance, *a, &b)
  JSI.class_for_schemas(match_to_instance(other_instance)).new(other_instance, *a, &b)
end

#schema_idString?

Returns an absolute id for the schema, with a json pointer fragment. nil if no parent of this schema defines an id.

Returns:

  • (String, nil)

    an absolute id for the schema, with a json pointer fragment. nil if no parent of this schema defines an id.



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/jsi/schema.rb', line 83

def schema_id
  return @schema_id if instance_variable_defined?(:@schema_id)
  @schema_id = begin
    # start from self and ascend parents looking for an 'id' property.
    # append a fragment to that id (appending to an existing fragment if there
    # is one) consisting of the path from that parent to our schema_node.
    node_for_id = self
    path_from_id_node = []
    done = false

    while !done
      node_content_for_id = node_for_id.node_content
      if node_for_id.is_a?(JSI::Schema) && node_content_for_id.respond_to?(:to_hash)
        parent_id = node_content_for_id.key?('$id') && node_content_for_id['$id'].respond_to?(:to_str) ? node_content_for_id['$id'].to_str :
          node_content_for_id.key?('id') && node_content_for_id['id'].respond_to?(:to_str) ? node_content_for_id['id'].to_str : nil
      end

      if parent_id || node_for_id.node_ptr.root?
        done = true
      else
        path_from_id_node.unshift(node_for_id.node_ptr.reference_tokens.last)
        node_for_id = node_for_id.parent_node
      end
    end
    if parent_id
      parent_auri = Addressable::URI.parse(parent_id)
      if parent_auri.fragment
        # add onto the fragment
        parent_id_path = JSI::JSON::Pointer.from_fragment(parent_auri.fragment).reference_tokens
        path_from_id_node = parent_id_path + path_from_id_node
        parent_auri.fragment = nil
      #else: no fragment so parent_id good as is
      end

      schema_id = parent_auri.merge(fragment: JSI::JSON::Pointer.new(path_from_id_node).fragment).to_s

      schema_id
    else
      nil
    end
  end
end

#subschemas_for_index(index) ⇒ Set<JSI::Schema>

returns a set of subschemas of this schema for the given array index, from keywords items and additionalItems.

Parameters:

  • index (Integer)

    the array index for which to find subschemas

Returns:

  • (Set<JSI::Schema>)

    subschemas of this schema for the given array index



183
184
185
186
187
188
189
# File 'lib/jsi/schema.rb', line 183

def subschemas_for_index(index)
  jsi_memoize(:subschemas_for_index, index) do |index|
    node_ptr.schema_subschema_ptrs_for_index(node_document, index).map do |ptr|
      ptr.evaluate(document_root_node).tap { |subschema| jsi_ensure_subschema_is_schema(subschema, ptr) }
    end.to_set
  end
end

#subschemas_for_property_name(property_name) ⇒ Set<JSI::Schema>

returns a set of subschemas of this schema for the given property name, from keywords properties, patternProperties, and additionalProperties.

Parameters:

  • property_name (String)

    the property name for which to find subschemas

Returns:

  • (Set<JSI::Schema>)

    subschemas of this schema for the given property_name



170
171
172
173
174
175
176
# File 'lib/jsi/schema.rb', line 170

def subschemas_for_property_name(property_name)
  jsi_memoize(:subschemas_for_property_name, property_name) do |property_name|
    node_ptr.schema_subschema_ptrs_for_property_name(node_document, property_name).map do |ptr|
      ptr.evaluate(document_root_node).tap { |subschema| jsi_ensure_subschema_is_schema(subschema, ptr) }
    end.to_set
  end
end

#validate_instance(other_instance) ⇒ true, false

Returns whether the given instance validates against this schema.

Returns:

  • (true, false)

    whether the given instance validates against this schema



214
215
216
# File 'lib/jsi/schema.rb', line 214

def validate_instance(other_instance)
  ::JSON::Validator.validate(JSI::Typelike.as_json(node_document), JSI::Typelike.as_json(other_instance), fragment: node_ptr.fragment)
end

#validate_instance!(other_instance) ⇒ true

Returns if this method does not raise, it returns true to indicate the instance is valid against this schema.

Returns:

  • (true)

    if this method does not raise, it returns true to indicate the instance is valid against this schema

Raises:

  • (::JSON::Schema::ValidationError)

    raises if the instance has validation errors against this schema



222
223
224
# File 'lib/jsi/schema.rb', line 222

def validate_instance!(other_instance)
  ::JSON::Validator.validate!(JSI::Typelike.as_json(node_document), JSI::Typelike.as_json(other_instance), fragment: node_ptr.fragment)
end

#validate_schematrue, false

Returns whether this schema validates against its metaschema.

Returns:

  • (true, false)

    whether this schema validates against its metaschema



234
235
236
# File 'lib/jsi/schema.rb', line 234

def validate_schema
  ::JSON::Validator.validate(JSI::Typelike.as_json(node_document), [], fragment: node_ptr.fragment, validate_schema: true, list: true)
end

#validate_schema!true

Returns if this method does not raise, it returns true to indicate this schema is valid against its metaschema.

Returns:

  • (true)

    if this method does not raise, it returns true to indicate this schema is valid against its metaschema

Raises:

  • (::JSON::Schema::ValidationError)

    raises if this schema has validation errors against its metaschema



242
243
244
# File 'lib/jsi/schema.rb', line 242

def validate_schema!
  ::JSON::Validator.validate!(JSI::Typelike.as_json(node_document), [], fragment: node_ptr.fragment, validate_schema: true, list: true)
end