Class: JSI::Base
- Inherits:
-
Object
- Object
- JSI::Base
- Includes:
- Enumerable, PathedNode, Schema::SchemaAncestorNode, Util::FingerprintHash, Util::Memoize
- Defined in:
- lib/jsi/base.rb
Overview
JSI::Base is the base class of every JSI instance of a JSON schema.
instances are described by a set of one or more JSON schemas. JSI dynamically creates a subclass of JSI::Base for each set of JSON schemas which describe an instance that is to be instantiated.
a JSI instance of such a subclass represents a JSON schema instance described by that set of schemas.
this subclass includes the JSI Schema Module of each schema it represents.
the method #jsi_schemas is defined to indicate the schemas the class represents.
the JSI::Base class itself is not intended to be instantiated.
Direct Known Subclasses
Defined Under Namespace
Classes: CannotSubscriptError
Instance Attribute Summary collapse
-
#jsi_document ⇒ Object
readonly
document containing the instance of this JSI at our #jsi_ptr.
-
#jsi_ptr ⇒ JSI::Ptr
readonly
Ptr pointing to this JSI's instance within our #jsi_document.
-
#jsi_root_node ⇒ JSI::Base
readonly
the JSI at the root of this JSI's document.
Class Method Summary collapse
-
.inspect ⇒ String
a string indicating a class name if one is defined, as well as the schema module name and/or schema URI of each schema the class represents.
-
.name ⇒ String
a constant name of this class.
-
.to_s ⇒ String
a string indicating a class name if one is defined, as well as the schema module name and/or schema URI of each schema the class represents.
Instance Method Summary collapse
-
#[](token, as_jsi: :auto, use_default: true) ⇒ JSI::Base, Object
subscripts to return a child value identified by the given token.
-
#[]=(token, value) ⇒ Object
assigns the subscript of the instance identified by the given token to the given value.
-
#as_json(*opt) ⇒ Object
a jsonifiable representation of the instance.
- #dup ⇒ Object
-
#each(*_) ⇒ Object
each is overridden by PathedHashNode or PathedArrayNode when appropriate.
-
#initialize(instance, jsi_document: nil, jsi_ptr: nil, jsi_root_node: nil, jsi_schema_base_uri: nil, jsi_schema_resource_ancestors: []) ⇒ Base
constructor
initializes this JSI from the given instance - instance is most commonly a parsed JSON document consisting of Hash, Array, or sometimes a basic type, but this is in no way enforced and a JSI may wrap any object.
-
#inspect ⇒ String
a string representing this JSI, indicating any named schemas and inspecting its instance.
-
#jsi_each_child_node {|JSI::Base| ... } ⇒ nil, Enumerator
yields a JSI of each node at or below this one in this JSI's document.
-
#jsi_fingerprint ⇒ Object
an opaque fingerprint of this JSI for Util::FingerprintHash.
-
#jsi_modified_copy {|Object| ... } ⇒ JSI::Base subclass
yields the content of this JSI's instance.
-
#jsi_parent_node ⇒ JSI::Base?
the immediate parent of this JSI.
-
#jsi_parent_nodes ⇒ Array<JSI::Base>
an array of JSI instances above this one in the document.
-
#jsi_schema_modules ⇒ Set<Module>
the set of JSI schema modules corresponding to the schemas that describe this JSI.
-
#jsi_schemas ⇒ JSI::SchemaSet
the set of schemas which describe this instance.
-
#jsi_select_children_leaf_first {|JSI::Base| ... } ⇒ JSI::Base
recursively selects child nodes of this JSI, returning a modified copy of self containing only child nodes for which the given block had a true-ish result.
-
#jsi_select_children_node_first {|JSI::Base| ... } ⇒ JSI::Base
recursively selects child nodes of this JSI, returning a modified copy of self containing only child nodes for which the given block had a true-ish result.
-
#jsi_valid? ⇒ Boolean
whether this JSI's instance is valid against all of its schemas.
-
#jsi_validate ⇒ JSI::Validation::FullResult
validates this JSI's instance against its schemas.
-
#pretty_print(q) ⇒ void
pretty-prints a representation of this JSI to the given printer.
Methods included from Util::FingerprintHash
Methods included from Schema::SchemaAncestorNode
#jsi_anchor_subschema, #jsi_anchor_subschemas, #jsi_resource_ancestor_uri
Methods included from PathedNode
Constructor Details
#initialize(instance, jsi_document: nil, jsi_ptr: nil, jsi_root_node: nil, jsi_schema_base_uri: nil, jsi_schema_resource_ancestors: []) ⇒ Base
initializes this JSI from the given instance - instance is most commonly a parsed JSON document consisting of Hash, Array, or sometimes a basic type, but this is in no way enforced and a JSI may wrap any object.
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 |
# File 'lib/jsi/base.rb', line 145 def initialize(instance, jsi_document: nil, jsi_ptr: nil, jsi_root_node: nil, jsi_schema_base_uri: nil, jsi_schema_resource_ancestors: [] ) unless respond_to?(:jsi_schemas) raise(TypeError, "cannot instantiate #{self.class.inspect} which has no method #jsi_schemas. it is recommended to instantiate JSIs from a schema using JSI::Schema#new_jsi.") end if instance.is_a?(JSI::Schema) raise(TypeError, "assigning a schema to a #{self.class.inspect} instance is incorrect. received: #{instance.pretty_inspect.chomp}") elsif instance.is_a?(JSI::Base) raise(TypeError, "assigning another JSI::Base instance to a #{self.class.inspect} instance is incorrect. received: #{instance.pretty_inspect.chomp}") end jsi_initialize_memos if instance == NOINSTANCE self.jsi_document = jsi_document self.jsi_ptr = jsi_ptr if @jsi_ptr.root? raise(Bug, "jsi_root_node cannot be specified for root JSI") if jsi_root_node @jsi_root_node = self else if !jsi_root_node.is_a?(JSI::Base) raise(TypeError, "jsi_root_node must be a JSI::Base; got: #{jsi_root_node.inspect}") end if !jsi_root_node.jsi_ptr.root? raise(Bug, "jsi_root_node ptr #{jsi_root_node.jsi_ptr.inspect} is not root") end @jsi_root_node = jsi_root_node end else raise(Bug, 'incorrect usage') if jsi_document || jsi_ptr || jsi_root_node @jsi_document = instance @jsi_ptr = Ptr[] @jsi_root_node = self end self.jsi_schema_base_uri = jsi_schema_base_uri self.jsi_schema_resource_ancestors = jsi_schema_resource_ancestors if self.jsi_instance.respond_to?(:to_hash) extend PathedHashNode end if self.jsi_instance.respond_to?(:to_ary) extend PathedArrayNode end end |
Instance Attribute Details
#jsi_document ⇒ Object (readonly)
document containing the instance of this JSI at our #jsi_ptr
204 205 206 |
# File 'lib/jsi/base.rb', line 204 def jsi_document @jsi_document end |
#jsi_ptr ⇒ JSI::Ptr (readonly)
Ptr pointing to this JSI's instance within our #jsi_document
208 209 210 |
# File 'lib/jsi/base.rb', line 208 def jsi_ptr @jsi_ptr end |
#jsi_root_node ⇒ JSI::Base (readonly)
the JSI at the root of this JSI's document
212 213 214 |
# File 'lib/jsi/base.rb', line 212 def jsi_root_node @jsi_root_node end |
Class Method Details
.inspect ⇒ String
a string indicating a class name if one is defined, as well as the schema module name and/or schema URI of each schema the class represents.
47 48 49 50 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 77 78 |
# File 'lib/jsi/base.rb', line 47 def inspect if !respond_to?(:jsi_class_schemas) super else schema_names = jsi_class_schemas.map do |schema| mod = schema.jsi_schema_module if mod.name && schema.schema_uri "#{mod.name} (#{schema.schema_uri})" elsif mod.name mod.name elsif schema.schema_uri schema.schema_uri.to_s else schema.jsi_ptr.uri.to_s end end if name && !in_schema_classes if jsi_class_schemas.empty? "#{name} (0 schemas)" else "#{name} (#{schema_names.join(', ')})" end else if schema_names.empty? "(JSI Schema Class for 0 schemas)" else "(JSI Schema Class: #{schema_names.join(', ')})" end end end end |
.name ⇒ String
a constant name of this class. this is generated from the schema module name or URI of each schema this class represents. nil if any represented schema has no schema module name or schema URI.
this generated name is not too pretty but can be more helpful than an anonymous class, especially in error messages.
108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/jsi/base.rb', line 108 def name unless instance_variable_defined?(:@in_schema_classes) const_name = schema_classes_const_name if super || !const_name || SchemaClasses.const_defined?(const_name) @in_schema_classes = false else SchemaClasses.const_set(const_name, self) @in_schema_classes = true end end super end |
.to_s ⇒ String
a string indicating a class name if one is defined, as well as the schema module name and/or schema URI of each schema the class represents.
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 109 110 111 |
# File 'lib/jsi/base.rb', line 80 def inspect if !respond_to?(:jsi_class_schemas) super else schema_names = jsi_class_schemas.map do |schema| mod = schema.jsi_schema_module if mod.name && schema.schema_uri "#{mod.name} (#{schema.schema_uri})" elsif mod.name mod.name elsif schema.schema_uri schema.schema_uri.to_s else schema.jsi_ptr.uri.to_s end end if name && !in_schema_classes if jsi_class_schemas.empty? "#{name} (0 schemas)" else "#{name} (#{schema_names.join(', ')})" end else if schema_names.empty? "(JSI Schema Class for 0 schemas)" else "(JSI Schema Class: #{schema_names.join(', ')})" end end end end |
Instance Method Details
#[](token, as_jsi: :auto, use_default: true) ⇒ JSI::Base, Object
subscripts to return a child value identified by the given token.
368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 |
# File 'lib/jsi/base.rb', line 368 def [](token, as_jsi: :auto, use_default: true) if respond_to?(:to_hash) token_in_range = jsi_node_content_hash_pubsend(:key?, token) value = jsi_node_content_hash_pubsend(:[], token) elsif respond_to?(:to_ary) token_in_range = jsi_node_content_ary_pubsend(:each_index).include?(token) value = jsi_node_content_ary_pubsend(:[], token) else raise(CannotSubscriptError, "cannot subscript (using token: #{token.inspect}) from instance: #{jsi_instance.pretty_inspect.chomp}") end begin subinstance_schemas = jsi_subinstance_schemas_memos[token: token, instance: jsi_node_content, subinstance: value] if token_in_range jsi_subinstance_as_jsi(value, subinstance_schemas, as_jsi) do jsi_subinstance_memos[token: token, subinstance_schemas: subinstance_schemas] end else if use_default defaults = Set.new subinstance_schemas.each do |subinstance_schema| if subinstance_schema.respond_to?(:to_hash) && subinstance_schema.key?('default') defaults << subinstance_schema['default'] end end end if use_default && defaults.size == 1 # use the default value # we are using #dup so that we get a modified copy of self, in which we set dup[token]=default. dup.tap { |o| o[token] = defaults.first }[token, as_jsi: as_jsi] else # I kind of want to just return nil here. the preferred mechanism for # a JSI's default value should be its schema. but returning nil ignores # any value returned by Hash#default/#default_proc. there's no compelling # reason not to support both, so I'll return that. value end end end end |
#[]=(token, value) ⇒ Object
assigns the subscript of the instance identified by the given token to the given value. if the value is a JSI, its instance is assigned instead of the JSI value itself.
416 417 418 419 420 421 422 423 424 425 |
# File 'lib/jsi/base.rb', line 416 def []=(token, value) unless respond_to?(:to_hash) || respond_to?(:to_ary) raise(NoMethodError, "cannot assign subscript (using token: #{token.inspect}) to instance: #{jsi_instance.pretty_inspect.chomp}") end if value.is_a?(Base) self[token] = value.jsi_instance else jsi_instance[token] = value end end |
#as_json(*opt) ⇒ Object
a jsonifiable representation of the instance
555 556 557 |
# File 'lib/jsi/base.rb', line 555 def as_json(*opt) Typelike.as_json(jsi_instance, *opt) end |
#dup ⇒ Object
488 489 490 |
# File 'lib/jsi/base.rb', line 488 def dup jsi_modified_copy(&:dup) end |
#each(*_) ⇒ Object
each is overridden by PathedHashNode or PathedArrayNode when appropriate. the base #each is not actually implemented, along with all the methods of Enumerable.
219 220 221 |
# File 'lib/jsi/base.rb', line 219 def each(*_) raise NoMethodError, "Enumerable methods and #each not implemented for instance that is not like a hash or array: #{jsi_instance.pretty_inspect.chomp}" end |
#inspect ⇒ String
a string representing this JSI, indicating any named schemas and inspecting its instance
494 495 496 |
# File 'lib/jsi/base.rb', line 494 def inspect "\#<#{jsi_object_group_text.join(' ')} #{jsi_instance.inspect}>" end |
#jsi_each_child_node {|JSI::Base| ... } ⇒ nil, Enumerator
yields a JSI of each node at or below this one in this JSI's document.
returns an Enumerator if no block is given.
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 |
# File 'lib/jsi/base.rb', line 229 def jsi_each_child_node(&block) return to_enum(__method__) unless block yield self if respond_to?(:to_hash) each_key do |k| self[k, as_jsi: true].jsi_each_child_node(&block) end elsif respond_to?(:to_ary) each_index do |i| self[i, as_jsi: true].jsi_each_child_node(&block) end end nil end |
#jsi_fingerprint ⇒ Object
an opaque fingerprint of this JSI for Util::FingerprintHash.
560 561 562 563 564 565 566 567 568 569 570 |
# File 'lib/jsi/base.rb', line 560 def jsi_fingerprint { class: jsi_class, jsi_document: jsi_document, jsi_ptr: jsi_ptr, # for instances in documents with schemas: jsi_resource_ancestor_uri: jsi_resource_ancestor_uri, # only defined for JSI::Schema instances: jsi_schema_instance_modules: is_a?(Schema) ? jsi_schema_instance_modules : nil, } end |
#jsi_modified_copy {|Object| ... } ⇒ JSI::Base subclass
yields the content of this JSI's instance. the block must result in a modified copy of the yielded instance (not destructively modifying it) which will be used to instantiate a new JSI with the modified content.
the result may have different schemas which describe it than this JSI's schemas, if conditional applicator schemas apply differently to the modified instance.
443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 |
# File 'lib/jsi/base.rb', line 443 def jsi_modified_copy(&block) if @jsi_ptr.root? modified_document = @jsi_ptr.modified_document_copy(@jsi_document, &block) self.class.new(Base::NOINSTANCE, jsi_document: modified_document, jsi_ptr: @jsi_ptr, jsi_schema_base_uri: @jsi_schema_base_uri, jsi_schema_resource_ancestors: @jsi_schema_resource_ancestors, # this can only be empty but included for consistency ) else modified_jsi_root_node = @jsi_root_node.jsi_modified_copy do |root| @jsi_ptr.modified_document_copy(root, &block) end @jsi_ptr.evaluate(modified_jsi_root_node, as_jsi: true) end end |
#jsi_parent_node ⇒ JSI::Base?
the immediate parent of this JSI. nil if there is no parent.
329 330 331 |
# File 'lib/jsi/base.rb', line 329 def jsi_parent_node jsi_parent_nodes.first end |
#jsi_parent_nodes ⇒ Array<JSI::Base>
an array of JSI instances above this one in the document.
316 317 318 319 320 321 322 323 324 |
# File 'lib/jsi/base.rb', line 316 def jsi_parent_nodes parent = jsi_root_node jsi_ptr.tokens.map do |token| parent.tap do parent = parent[token, as_jsi: true] end end.reverse end |
#jsi_schema_modules ⇒ Set<Module>
the set of JSI schema modules corresponding to the schemas that describe this JSI
429 430 431 |
# File 'lib/jsi/base.rb', line 429 def jsi_schema_modules jsi_schemas.map(&:jsi_schema_module).to_set.freeze end |
#jsi_schemas ⇒ JSI::SchemaSet
the set of schemas which describe this instance
|
# File 'lib/jsi/base.rb', line 197
|
#jsi_select_children_leaf_first {|JSI::Base| ... } ⇒ JSI::Base
recursively selects child nodes of this JSI, returning a modified copy of self containing only child nodes for which the given block had a true-ish result.
this method recursively descends child nodes before yielding each node, so leaf nodes are yielded before their parents.
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 |
# File 'lib/jsi/base.rb', line 287 def jsi_select_children_leaf_first(&block) jsi_modified_copy do |instance| if respond_to?(:to_hash) res = instance.class.new each_key do |k| v = self[k, as_jsi: true].jsi_select_children_leaf_first(&block) if yield(v) res[k] = v.jsi_node_content end end res elsif respond_to?(:to_ary) res = instance.class.new each_index do |i| e = self[i, as_jsi: true].jsi_select_children_leaf_first(&block) if yield(e) res << e.jsi_node_content end end res else instance end end end |
#jsi_select_children_node_first {|JSI::Base| ... } ⇒ JSI::Base
recursively selects child nodes of this JSI, returning a modified copy of self containing only child nodes for which the given block had a true-ish result.
this method yields a node before recursively descending to its child nodes, so leaf nodes are yielded last, after their parents. if a node is not selected, its children are never recursed.
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 |
# File 'lib/jsi/base.rb', line 253 def jsi_select_children_node_first(&block) jsi_modified_copy do |instance| if respond_to?(:to_hash) res = instance.class.new each_key do |k| v = self[k, as_jsi: true] if yield(v) res[k] = v.jsi_select_children_node_first(&block).jsi_node_content end end res elsif respond_to?(:to_ary) res = instance.class.new each_index do |i| e = self[i, as_jsi: true] if yield(e) res << e.jsi_select_children_node_first(&block).jsi_node_content end end res else instance end end end |
#jsi_valid? ⇒ Boolean
whether this JSI's instance is valid against all of its schemas
469 470 471 |
# File 'lib/jsi/base.rb', line 469 def jsi_valid? jsi_schemas.instance_valid?(self) end |
#jsi_validate ⇒ JSI::Validation::FullResult
validates this JSI's instance against its schemas
463 464 465 |
# File 'lib/jsi/base.rb', line 463 def jsi_validate jsi_schemas.instance_validate(self) end |
#pretty_print(q) ⇒ void
This method returns an undefined value.
pretty-prints a representation of this JSI to the given printer
500 501 502 503 504 505 506 507 508 509 510 511 |
# File 'lib/jsi/base.rb', line 500 def pretty_print(q) q.text '#<' q.text jsi_object_group_text.join(' ') q.group_sub { q.nest(2) { q.breakable ' ' q.pp jsi_instance } } q.breakable '' q.text '>' end |