Class: JSI::Base
- Inherits:
-
Object
- Object
- JSI::Base
- Includes:
- 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
Modules: ArrayNode, Enumerable, HashNode 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.
Attributes included from Schema::SchemaAncestorNode
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
-
#initialize(jsi_document, jsi_ptr: Ptr[], jsi_root_node: nil, jsi_schema_base_uri: nil, jsi_schema_resource_ancestors: Util::EMPTY_ARY) ⇒ Base
constructor
private
initializes a JSI whose instance is in the given document at the given pointer.
-
#inspect ⇒ String
(also: #to_s)
a string representing this JSI, indicating any named schemas and inspecting its instance.
-
#jmespath_search(expression, **runtime_options) ⇒ Array, ...
queries this JSI using the JMESPath Ruby gem.
-
#jsi_ancestor_nodes ⇒ Array<JSI::Base>
ancestor JSI instances from this node up to the root.
-
#jsi_descendent_node(ptr) ⇒ JSI::Base
the descendent node at the given pointer.
-
#jsi_each_descendent_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::Private::FingerprintHash.
-
#jsi_modified_copy {|Object| ... } ⇒ JSI::Base subclass
yields the content of this JSI's instance.
-
#jsi_node_content ⇒ Object
(also: #jsi_instance)
the content of this node in our #jsi_document at our #jsi_ptr.
-
#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_descendents_leaf_first {|JSI::Base| ... } ⇒ JSI::Base
(also: #jsi_select_children_leaf_first)
recursively selects descendent nodes of this JSI, returning a modified copy of self containing only descendent nodes for which the given block had a true-ish result.
-
#jsi_select_descendents_node_first {|JSI::Base| ... } ⇒ JSI::Base
(also: #jsi_select_children_node_first)
recursively selects descendent nodes of this JSI, returning a modified copy of self containing only descendent 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 Schema::SchemaAncestorNode
#jsi_anchor_subschema, #jsi_anchor_subschemas, #jsi_resource_ancestor_uri, #jsi_schema_resource_ancestors
Constructor Details
#initialize(jsi_document, jsi_ptr: Ptr[], jsi_root_node: nil, jsi_schema_base_uri: nil, jsi_schema_resource_ancestors: Util::EMPTY_ARY) ⇒ Base
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
initializes a JSI whose instance is in the given document at the given pointer.
this is a private api - users should look elsewhere to instantiate JSIs, in particular:
- JSI.new_schema and Schema::DescribesSchema#new_schema to instantiate schemas
- Schema#new_jsi to instantiate schema instances
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
# File 'lib/jsi/base.rb', line 129 def initialize(jsi_document, jsi_ptr: Ptr[], jsi_root_node: nil, jsi_schema_base_uri: nil, jsi_schema_resource_ancestors: Util::EMPTY_ARY ) raise(Bug, "no #jsi_schemas") unless respond_to?(:jsi_schemas) jsi_initialize_memos self.jsi_document = jsi_document self.jsi_ptr = jsi_ptr if @jsi_ptr.root? raise(Bug, "jsi_root_node specified for root JSI") if jsi_root_node @jsi_root_node = self else raise(Bug, "jsi_root_node is not JSI::Base") if !jsi_root_node.is_a?(JSI::Base) raise(Bug, "jsi_root_node ptr is not root") if !jsi_root_node.jsi_ptr.root? @jsi_root_node = jsi_root_node end self.jsi_schema_base_uri = jsi_schema_base_uri self.jsi_schema_resource_ancestors = jsi_schema_resource_ancestors if jsi_instance.is_a?(JSI::Base) raise(TypeError, "a JSI::Base instance must not be another JSI::Base. received: #{jsi_instance.pretty_inspect.chomp}") end end |
Instance Attribute Details
#jsi_document ⇒ Object (readonly)
document containing the instance of this JSI at our #jsi_ptr
164 165 166 |
# File 'lib/jsi/base.rb', line 164 def jsi_document @jsi_document end |
#jsi_ptr ⇒ JSI::Ptr (readonly)
Ptr pointing to this JSI's instance within our #jsi_document
168 169 170 |
# File 'lib/jsi/base.rb', line 168 def jsi_ptr @jsi_ptr end |
#jsi_root_node ⇒ JSI::Base (readonly)
the JSI at the root of this JSI's document
172 173 174 |
# File 'lib/jsi/base.rb', line 172 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.
40 41 42 43 44 45 46 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 |
# File 'lib/jsi/base.rb', line 40 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.
102 103 104 105 106 107 108 109 110 111 112 113 |
# File 'lib/jsi/base.rb', line 102 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.
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 |
# File 'lib/jsi/base.rb', line 73 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.
358 359 360 361 362 363 364 365 366 367 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 |
# File 'lib/jsi/base.rb', line 358 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, includes: SchemaClasses.includes_for(value), ] end else if use_default defaults = Set.new subinstance_schemas.each do |subinstance_schema| if subinstance_schema.keyword?('default') defaults << subinstance_schema.jsi_node_content['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.
410 411 412 413 414 415 416 417 418 419 |
# File 'lib/jsi/base.rb', line 410 def []=(token, value) unless respond_to?(:to_hash) || respond_to?(:to_ary) raise(CannotSubscriptError, "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
537 538 539 |
# File 'lib/jsi/base.rb', line 537 def as_json(*opt) Util.as_json(jsi_instance, *opt) end |
#dup ⇒ Object
481 482 483 |
# File 'lib/jsi/base.rb', line 481 def dup jsi_modified_copy(&:dup) end |
#inspect ⇒ String Also known as: to_s
a string representing this JSI, indicating any named schemas and inspecting its instance
487 488 489 |
# File 'lib/jsi/base.rb', line 487 def inspect "\#<#{jsi_object_group_text.join(' ')} #{jsi_instance.inspect}>" end |
#jmespath_search(expression, **runtime_options) ⇒ Array, ...
queries this JSI using the JMESPath Ruby gem. see https://jmespath.org/ to learn the JMESPath query language.
the JMESPath gem is not a dependency of JSI, so must be installed / added to your Gemfile to use.
e.g. gem 'jmespath', '~> 1.5'
. note that versions below 1.5 are not compatible with JSI.
475 476 477 478 479 |
# File 'lib/jsi/base.rb', line 475 def jmespath_search(expression, **) Util.require_jmespath JMESPath.search(expression, self, **) end |
#jsi_ancestor_nodes ⇒ Array<JSI::Base>
ancestor JSI instances from this node up to the root. this node itself is always its own first ancestor.
302 303 304 305 306 307 308 309 310 311 312 |
# File 'lib/jsi/base.rb', line 302 def jsi_ancestor_nodes ancestors = [] ancestor = jsi_root_node ancestors << ancestor jsi_ptr.tokens.each do |token| ancestor = ancestor[token, as_jsi: true] ancestors << ancestor end ancestors.reverse!.freeze end |
#jsi_descendent_node(ptr) ⇒ JSI::Base
the descendent node at the given pointer
318 319 320 321 |
# File 'lib/jsi/base.rb', line 318 def jsi_descendent_node(ptr) descendent = Ptr.ary_ptr(ptr).evaluate(self, as_jsi: true) descendent end |
#jsi_each_descendent_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.
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 |
# File 'lib/jsi/base.rb', line 189 def jsi_each_descendent_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_descendent_node(&block) end elsif respond_to?(:to_ary) each_index do |i| self[i, as_jsi: true].jsi_each_descendent_node(&block) end end nil end |
#jsi_fingerprint ⇒ Object
an opaque fingerprint of this JSI for Util::Private::FingerprintHash.
542 543 544 545 546 547 548 549 550 551 552 |
# File 'lib/jsi/base.rb', line 542 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 modified in place, which would alter this JSI as well) which will be used to instantiate and return 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.
437 438 439 440 441 442 443 444 445 446 447 448 449 |
# File 'lib/jsi/base.rb', line 437 def jsi_modified_copy(&block) if @jsi_ptr.root? modified_document = @jsi_ptr.modified_document_copy(@jsi_document, &block) jsi_schemas.new_jsi(modified_document, uri: jsi_schema_base_uri, ) else modified_jsi_root_node = @jsi_root_node.jsi_modified_copy do |root| @jsi_ptr.modified_document_copy(root, &block) end modified_jsi_root_node.jsi_descendent_node(@jsi_ptr) end end |
#jsi_node_content ⇒ Object Also known as: jsi_instance
the content of this node in our #jsi_document at our #jsi_ptr. the same as #jsi_instance.
175 176 177 178 |
# File 'lib/jsi/base.rb', line 175 def jsi_node_content content = jsi_ptr.evaluate(jsi_document) content end |
#jsi_parent_node ⇒ JSI::Base?
the immediate parent of this JSI. nil if there is no parent.
295 296 297 |
# File 'lib/jsi/base.rb', line 295 def jsi_parent_node jsi_ptr.root? ? nil : jsi_root_node.jsi_descendent_node(jsi_ptr.parent) end |
#jsi_parent_nodes ⇒ Array<JSI::Base>
an array of JSI instances above this one in the document.
282 283 284 285 286 287 288 289 290 |
# File 'lib/jsi/base.rb', line 282 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
423 424 425 |
# File 'lib/jsi/base.rb', line 423 def jsi_schema_modules Util.ensure_module_set(jsi_schemas.map(&:jsi_schema_module)) end |
#jsi_schemas ⇒ JSI::SchemaSet
the set of schemas which describe this instance
|
# File 'lib/jsi/base.rb', line 157
|
#jsi_select_descendents_leaf_first {|JSI::Base| ... } ⇒ JSI::Base Also known as: jsi_select_children_leaf_first
recursively selects descendent nodes of this JSI, returning a modified copy of self containing only descendent 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.
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 |
# File 'lib/jsi/base.rb', line 250 def jsi_select_descendents_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_descendents_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_descendents_leaf_first(&block) if yield(e) res << e.jsi_node_content end end res else instance end end end |
#jsi_select_descendents_node_first {|JSI::Base| ... } ⇒ JSI::Base Also known as: jsi_select_children_node_first
recursively selects descendent nodes of this JSI, returning a modified copy of self containing only descendent 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 descendents are never recursed.
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 |
# File 'lib/jsi/base.rb', line 213 def jsi_select_descendents_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_descendents_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_descendents_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
460 461 462 |
# File 'lib/jsi/base.rb', line 460 def jsi_valid? jsi_schemas.instance_valid?(self) end |
#jsi_validate ⇒ JSI::Validation::FullResult
validates this JSI's instance against its schemas
454 455 456 |
# File 'lib/jsi/base.rb', line 454 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
495 496 497 498 499 500 501 502 503 504 505 506 |
# File 'lib/jsi/base.rb', line 495 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 |