Class: JSI::Schema
- Inherits:
-
Object
- Object
- JSI::Schema
- Includes:
- FingerprintHash, Memoize
- Defined in:
- lib/jsi/schema.rb
Overview
JSI::Schema represents a JSON Schema. initialized from a Hash-like schema object, JSI::Schema is a relatively simple class to abstract useful methods applied to a JSON Schema.
Instance Attribute Summary collapse
-
#schema_node ⇒ JSI::PathedNode
(also: #schema_object)
readonly
A JSI::PathedNode (JSI::JSON::Node or JSI::Base) for the schema.
Class Method Summary collapse
Instance Method Summary collapse
-
#[](property_name) ⇒ JSI::Base, ...
Property value from the schema_object.
-
#as_json(*opt) ⇒ Object
Returns a jsonifiable representation of this schema.
- #default_value ⇒ Object
- #default_value? ⇒ Boolean
-
#described_object_property_names ⇒ Set
Any object property names this schema indicates may be present on its instances.
-
#fingerprint ⇒ Object
An opaque fingerprint of this Schema for FingerprintHash.
-
#fully_validate_instance(other_instance) ⇒ Array<String>
Array of schema validation error messages for the given instance against this schema.
-
#fully_validate_schema ⇒ Array<String>
Array of schema validation error messages for this schema, validated against its metaschema.
-
#initialize(schema_object) ⇒ Schema
constructor
initializes a schema from a given JSI::Base, JSI::JSON::Node, or hash.
-
#inspect ⇒ String
(also: #to_s)
A string representing this Schema.
-
#jsi_schema_class ⇒ Class subclassing JSI::Base
(also: #schema_class)
Shortcut for JSI.class_for_schema(schema).
-
#match_to_instance(other_instance) ⇒ JSI::Schema
if this schema is a oneOf, allOf, anyOf schema, #match_to_instance finds one of the subschemas that matches the given instance and returns it.
-
#new_jsi(other_instance, *a, &b) ⇒ JSI::Base
calls #new on the class for this schema with the given arguments.
-
#object_group_text ⇒ String
A string for #instance and #pretty_print including the schema_id.
-
#pretty_print(q) ⇒ void
pretty-prints a representation this Schema to the given printer.
-
#schema_id ⇒ String
An absolute id for the schema, with a json pointer fragment.
-
#subschema_for_index(index_) ⇒ JSI::Schema?
A subschema from
itemsoradditionalItemsfor the given index. -
#subschema_for_property(property_name_) ⇒ JSI::Schema?
A subschema from
properties,patternProperties, oradditionalPropertiesfor the given property_name. -
#validate_instance(other_instance) ⇒ true, false
Whether the given instance validates against this schema.
-
#validate_instance!(other_instance) ⇒ true
If this method does not raise, it returns true to indicate the instance is valid against this schema.
-
#validate_schema ⇒ true, false
Whether this schema validates against its metaschema.
-
#validate_schema! ⇒ true
If this method does not raise, it returns true to indicate this schema is valid against its metaschema.
Methods included from FingerprintHash
Methods included from Memoize
Constructor Details
#initialize(schema_object) ⇒ Schema
initializes a schema from a given JSI::Base, JSI::JSON::Node, or hash. Boolean schemas are instantiated as their equivalent hash ({} for true and => {} for false).
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
# File 'lib/jsi/schema.rb', line 27 def initialize(schema_object) if schema_object.is_a?(JSI::Schema) raise(TypeError, "will not instantiate Schema from another Schema: #{schema_object.pretty_inspect.chomp}") elsif schema_object.is_a?(JSI::PathedNode) @schema_node = JSI.deep_stringify_symbol_keys(schema_object.deref) elsif schema_object.respond_to?(:to_hash) @schema_node = JSI::JSON::Node.new_doc(JSI.deep_stringify_symbol_keys(schema_object)) elsif schema_object == true @schema_node = JSI::JSON::Node.new_doc({}) elsif schema_object == false @schema_node = JSI::JSON::Node.new_doc({"not" => {}}) else raise(TypeError, "cannot instantiate Schema from: #{schema_object.pretty_inspect.chomp}") end end |
Instance Attribute Details
#schema_node ⇒ JSI::PathedNode (readonly) Also known as: schema_object
Returns a JSI::PathedNode (JSI::JSON::Node or JSI::Base) for the schema.
44 45 46 |
# File 'lib/jsi/schema.rb', line 44 def schema_node @schema_node end |
Class Method Details
.from_object(schema_object) ⇒ JSI::Schema
14 15 16 17 18 19 20 |
# File 'lib/jsi/schema.rb', line 14 def from_object(schema_object) if schema_object.is_a?(Schema) schema_object else new(schema_object) end end |
Instance Method Details
#[](property_name) ⇒ JSI::Base, ...
Returns property value from the schema_object.
50 51 52 |
# File 'lib/jsi/schema.rb', line 50 def [](property_name) schema_object[property_name] end |
#as_json(*opt) ⇒ Object
Returns a jsonifiable representation of this schema
301 302 303 |
# File 'lib/jsi/schema.rb', line 301 def as_json(*opt) Typelike.as_json(schema_object, *opt) end |
#default_value ⇒ Object
218 219 220 221 222 223 224 225 226 227 228 |
# File 'lib/jsi/schema.rb', line 218 def default_value if schema_node.key?('default') if schema_node['default'].respond_to?(:to_ary) || schema_node['default'].respond_to?(:to_hash) schema_class.new(schema_node['default']) else schema_node['default'] end else nil end end |
#default_value? ⇒ Boolean
230 231 232 |
# File 'lib/jsi/schema.rb', line 230 def default_value? schema_node.key?('default') end |
#described_object_property_names ⇒ Set
Returns any object property names this schema indicates may be present on its instances. this includes, if present: keys of this schema's "properties" object; entries of this schema's array of "required" property keys. if this schema has oneOf/allOf/anyOf subschemas, those schemas are checked (recursively) for their described object property names.
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 |
# File 'lib/jsi/schema.rb', line 198 def described_object_property_names memoize(:described_object_property_names) do Set.new.tap do |property_names| if schema_node['properties'].respond_to?(:to_hash) property_names.merge(schema_node['properties'].keys) end if schema_node['required'].respond_to?(:to_ary) property_names.merge(schema_node['required'].to_ary) end # we _could_ look at the properties of 'default' and each 'enum' but ... nah. # we should look at dependencies (TODO). %w(oneOf allOf anyOf).select { |k| schema_node[k].respond_to?(:to_ary) }.each do |schemas_key| schema_node[schemas_key].map(&:deref).map do |someof_node| property_names.merge(self.class.new(someof_node).described_object_property_names) end end end end end |
#fingerprint ⇒ Object
Returns an opaque fingerprint of this Schema for FingerprintHash.
306 307 308 |
# File 'lib/jsi/schema.rb', line 306 def fingerprint {class: self.class, schema_ptr: schema_node.node_ptr, schema_document: JSI::Typelike.as_json(schema_node.node_document)} end |
#fully_validate_instance(other_instance) ⇒ Array<String>
Returns array of schema validation error messages for the given instance against this schema.
236 237 238 |
# File 'lib/jsi/schema.rb', line 236 def fully_validate_instance(other_instance) ::JSON::Validator.fully_validate(JSI::Typelike.as_json(schema_node.node_document), JSI::Typelike.as_json(other_instance), fragment: schema_node.node_ptr.fragment) end |
#fully_validate_schema ⇒ Array<String>
Returns array of schema validation error messages for this schema, validated against its metaschema. a default metaschema is assumed if the schema does not specify a $schema.
256 257 258 |
# File 'lib/jsi/schema.rb', line 256 def fully_validate_schema ::JSON::Validator.fully_validate(JSI::Typelike.as_json(schema_node.node_document), [], fragment: schema_node.node_ptr.fragment, validate_schema: true, list: true) end |
#inspect ⇒ String Also known as: to_s
Returns a string representing this Schema.
279 280 281 |
# File 'lib/jsi/schema.rb', line 279 def inspect "\#<#{self.class.inspect} #{object_group_text} #{schema_object.inspect}>" end |
#jsi_schema_class ⇒ Class subclassing JSI::Base Also known as: schema_class
Returns shortcut for JSI.class_for_schema(schema).
111 112 113 |
# File 'lib/jsi/schema.rb', line 111 def jsi_schema_class JSI.class_for_schema(self) end |
#match_to_instance(other_instance) ⇒ JSI::Schema
if this schema is a oneOf, allOf, anyOf schema, #match_to_instance finds one of the subschemas that matches the given instance and returns it. if there are no matching *Of schemas, this schema is returned.
131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
# File 'lib/jsi/schema.rb', line 131 def match_to_instance(other_instance) # matching oneOf is good here. one schema for one instance. # matching anyOf is okay. there could be more than one schema matched. it's often just one. if more # than one is a match, you just get the first one. %w(oneOf anyOf).select { |k| schema_node[k].respond_to?(:to_ary) }.each do |someof_key| schema_node[someof_key].map(&:deref).map do |someof_node| someof_schema = self.class.new(someof_node) if someof_schema.validate_instance(other_instance) return someof_schema.match_to_instance(other_instance) end end end return self end |
#new_jsi(other_instance, *a, &b) ⇒ JSI::Base
calls #new on the class for this schema with the given arguments. for parameters, see JSI::Base#initialize documentation.
121 122 123 |
# File 'lib/jsi/schema.rb', line 121 def new_jsi(other_instance, *a, &b) jsi_schema_class.new(other_instance, *a, &b) end |
#object_group_text ⇒ String
Returns a string for #instance and #pretty_print including the schema_id.
274 275 276 |
# File 'lib/jsi/schema.rb', line 274 def object_group_text "schema_id=#{schema_id}" end |
#pretty_print(q) ⇒ void
This method returns an undefined value.
pretty-prints a representation this Schema to the given printer
286 287 288 289 290 291 292 293 294 295 296 297 298 |
# File 'lib/jsi/schema.rb', line 286 def pretty_print(q) q.instance_exec(self) do |obj| text "\#<#{obj.class.inspect} #{obj.object_group_text}" group_sub { nest(2) { breakable ' ' pp obj.schema_object } } breakable '' text '>' end end |
#schema_id ⇒ String
Returns an absolute id for the schema, with a json pointer fragment.
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 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 |
# File 'lib/jsi/schema.rb', line 55 def schema_id @schema_id ||= begin # start from schema_node 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 = schema_node path_from_id_node = [] done = false while !done # TODO: track what parents are schemas. somehow. # look at 'id' if node_for_id is a schema, or the document root. # decide whether to look at '$id' for all parent nodes or also just schemas. if node_for_id.respond_to?(:to_hash) if node_for_id.node_ptr.root? || node_for_id.object_id == schema_node.object_id # I'm only looking at 'id' for the document root and the schema node # until I track what parents are schemas. parent_id = node_for_id['$id'] || node_for_id['id'] else # will look at '$id' everywhere since it is less likely to show up outside schemas than # 'id', but it will be better to only look at parents that are schemas for this too. parent_id = node_for_id['$id'] end 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) else node_for_id = schema_node.document_root_node validator = ::JSON::Validator.new(Typelike.as_json(node_for_id), nil) # TODO not good instance_exec'ing into another library's ivars parent_auri = validator.instance_exec { @base_schema }.uri end 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 fragment = JSI::JSON::Pointer.new(path_from_id_node).fragment schema_id = parent_auri.to_s + fragment schema_id end end |
#subschema_for_index(index_) ⇒ JSI::Schema?
Returns a subschema from items or
additionalItems for the given index.
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 |
# File 'lib/jsi/schema.rb', line 176 def subschema_for_index(index_) memoize(:subschema_for_index, index_) do |index| if schema_object['items'].respond_to?(:to_ary) if index < schema_object['items'].size self.class.new(schema_object['items'][index]) elsif schema_object['additionalItems'].respond_to?(:to_hash) self.class.new(schema_object['additionalItems']) end elsif schema_object['items'].respond_to?(:to_hash) self.class.new(schema_object['items']) else nil end end end |
#subschema_for_property(property_name_) ⇒ JSI::Schema?
Returns a subschema from properties,
patternProperties, or additionalProperties for the given
property_name.
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
# File 'lib/jsi/schema.rb', line 150 def subschema_for_property(property_name_) memoize(:subschema_for_property, property_name_) do |property_name| if schema_object['properties'].respond_to?(:to_hash) && schema_object['properties'][property_name].respond_to?(:to_hash) self.class.new(schema_object['properties'][property_name]) else if schema_object['patternProperties'].respond_to?(:to_hash) _, pattern_schema_object = schema_object['patternProperties'].detect do |pattern, _| property_name.to_s =~ Regexp.new(pattern) # TODO map pattern to ruby syntax end end if pattern_schema_object self.class.new(pattern_schema_object) else if schema_object['additionalProperties'].respond_to?(:to_hash) self.class.new(schema_object['additionalProperties']) else nil end end end end end |
#validate_instance(other_instance) ⇒ true, false
Returns whether the given instance validates against this schema.
241 242 243 |
# File 'lib/jsi/schema.rb', line 241 def validate_instance(other_instance) ::JSON::Validator.validate(JSI::Typelike.as_json(schema_node.node_document), JSI::Typelike.as_json(other_instance), fragment: schema_node.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.
249 250 251 |
# File 'lib/jsi/schema.rb', line 249 def validate_instance!(other_instance) ::JSON::Validator.validate!(JSI::Typelike.as_json(schema_node.node_document), JSI::Typelike.as_json(other_instance), fragment: schema_node.node_ptr.fragment) end |
#validate_schema ⇒ true, false
Returns whether this schema validates against its metaschema.
261 262 263 |
# File 'lib/jsi/schema.rb', line 261 def validate_schema ::JSON::Validator.validate(JSI::Typelike.as_json(schema_node.node_document), [], fragment: schema_node.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.
269 270 271 |
# File 'lib/jsi/schema.rb', line 269 def validate_schema! ::JSON::Validator.validate!(JSI::Typelike.as_json(schema_node.node_document), [], fragment: schema_node.node_ptr.fragment, validate_schema: true, list: true) end |