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.
-
#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
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, ...
50 51 52 |
# File 'lib/jsi/schema.rb', line 50 def [](property_name) schema_object[property_name] end |
#as_json(*opt) ⇒ Object
283 284 285 |
# File 'lib/jsi/schema.rb', line 283 def as_json(*opt) Typelike.as_json(schema_object, *opt) end |
#described_object_property_names ⇒ Set
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 |
# File 'lib/jsi/schema.rb', line 197 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 should look at dependencies (TODO). if schema_node['allOf'].respond_to?(:to_ary) schema_node['allOf'].map(&:deref).map do |allOf_node| property_names.merge(self.class.new(allOf_node).described_object_property_names) end end end end end |
#fingerprint ⇒ Object
288 289 290 |
# File 'lib/jsi/schema.rb', line 288 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>
218 219 220 |
# File 'lib/jsi/schema.rb', line 218 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>
238 239 240 |
# File 'lib/jsi/schema.rb', line 238 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
261 262 263 |
# File 'lib/jsi/schema.rb', line 261 def inspect "\#<#{self.class.inspect} #{object_group_text} #{schema_object.inspect}>" end |
#jsi_schema_class ⇒ Class subclassing JSI::Base Also known as: schema_class
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
256 257 258 |
# File 'lib/jsi/schema.rb', line 256 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
268 269 270 271 272 273 274 275 276 277 278 279 280 |
# File 'lib/jsi/schema.rb', line 268 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
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?
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?
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
223 224 225 |
# File 'lib/jsi/schema.rb', line 223 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.
231 232 233 |
# File 'lib/jsi/schema.rb', line 231 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
243 244 245 |
# File 'lib/jsi/schema.rb', line 243 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.
251 252 253 |
# File 'lib/jsi/schema.rb', line 251 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 |