Class: JSI::MetaschemaNode

Inherits:
Base
  • Object
show all
Defined in:
lib/jsi/metaschema_node.rb

Overview

a MetaschemaNode is a JSI instance representing a node in a document which contains a metaschema. the root of the metaschema is pointed to by metaschema_root_ptr. the schema describing the root of the document is pointed to by root_schema_ptr.

like JSI::Base's normal subclasses, this class represents an instance of a schema set, an instance which may itself be a schema. unlike JSI::Base, the document containing the instance and its schemas is the same, and a schema (the metaschema) may be an instance of itself.

unlike JSI::Base's normal subclasses, the schemas describing the instance are not part of the class. since the metaschema describes itself, attempting to construct a class from the JSI Schema Module of a schema which is itself an instance of that class results in a causality loop. instead, a MetaschemaNode calculates its #jsi_schemas and extends itself with their JSI Schema modules during initialization. the MetaschemaNode of the metaschema is extended with its own JSI Schema Module.

if the MetaschemaNode's schemas include its self, it is extended with JSI::Metaschema.

a MetaschemaNode is extended with JSI::Schema when it represents a schema - this is the case when the metaschema is one of its schemas.

Defined Under Namespace

Classes: BootstrapSchema

Instance Attribute Summary collapse

Attributes inherited from Base

#jsi_document, #jsi_ptr, #jsi_root_node

Attributes included from Schema::SchemaAncestorNode

#jsi_schema_base_uri

Instance Method Summary collapse

Methods inherited from Base

#[]=, #as_json, #dup, inspect, #inspect, #jmespath_search, #jsi_ancestor_nodes, #jsi_descendent_node, #jsi_each_descendent_node, #jsi_node_content, #jsi_parent_node, #jsi_parent_nodes, #jsi_schema_modules, #jsi_select_descendents_leaf_first, #jsi_select_descendents_node_first, #jsi_valid?, #jsi_validate, name, #pretty_print, to_s

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[], schema_implementation_modules:, metaschema_root_ptr: Ptr[], root_schema_ptr: Ptr[], jsi_schema_base_uri: nil, jsi_root_node: nil) ⇒ MetaschemaNode

Returns a new instance of MetaschemaNode.

Parameters:

  • jsi_document

    the document containing the metaschema

  • jsi_ptr (JSI::Ptr) (defaults to: Ptr[])

    ptr to this MetaschemaNode in jsi_document

  • schema_implementation_modules (Enumerable<Module>)

    modules which implement the functionality of the schema. these are included on the Schema#jsi_schema_module of the metaschema. they extend any schema described by the metaschema, including those in the document containing the metaschema, and the metaschema itself. see Schema#describes_schema! param schema_implementation_modules.

  • metaschema_root_ptr (JSI::Ptr) (defaults to: Ptr[])

    ptr to the root of the metaschema in the jsi_document

  • root_schema_ptr (JSI::Ptr) (defaults to: Ptr[])

    ptr to the schema describing the root of the jsi_document

Raises:



35
36
37
38
39
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
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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/jsi/metaschema_node.rb', line 35

def initialize(
    jsi_document,
    jsi_ptr: Ptr[],
    schema_implementation_modules: ,
    metaschema_root_ptr: Ptr[],
    root_schema_ptr: Ptr[],
    jsi_schema_base_uri: nil,
    jsi_root_node: nil
)
  jsi_initialize_memos

  self.jsi_document = jsi_document
  self.jsi_ptr = jsi_ptr
  @schema_implementation_modules = Util.ensure_module_set(schema_implementation_modules)
  @metaschema_root_ptr = metaschema_root_ptr
  @root_schema_ptr = root_schema_ptr
  raise(Bug, 'jsi_root_node') if jsi_ptr.root? ^ !jsi_root_node
  @jsi_root_node = jsi_ptr.root? ? self : jsi_root_node

  if jsi_ptr.root? && jsi_schema_base_uri
    raise(NotImplementedError, "unsupported jsi_schema_base_uri on metaschema document root")
  end
  self.jsi_schema_base_uri = jsi_schema_base_uri

  jsi_node_content = self.jsi_node_content

  if jsi_node_content.respond_to?(:to_hash)
    extend HashNode
  end
  if jsi_node_content.respond_to?(:to_ary)
    extend ArrayNode
  end

  instance_for_schemas = jsi_document
  bootstrap_schema_class = JSI::SchemaClasses.bootstrap_schema_class(schema_implementation_modules)
  root_bootstrap_schema = bootstrap_schema_class.new(
    jsi_document,
    jsi_ptr: root_schema_ptr,
    jsi_schema_base_uri: nil, # supplying jsi_schema_base_uri on root bootstrap schema is not supported
  )
  our_bootstrap_schemas = jsi_ptr.tokens.inject(SchemaSet[root_bootstrap_schema]) do |bootstrap_schemas, tok|
    child_indicated_schemas = bootstrap_schemas.child_applicator_schemas(tok, instance_for_schemas)
    child_schemas = child_indicated_schemas.inplace_applicator_schemas(instance_for_schemas[tok])
    instance_for_schemas = instance_for_schemas[tok]
    child_schemas
  end

  our_bootstrap_schemas.each do |bootstrap_schema|
    if bootstrap_schema.jsi_ptr == metaschema_root_ptr
      # this is described by the metaschema, i.e. this is a schema
      schema_implementation_modules.each do |schema_implementation_module|
        extend schema_implementation_module
      end
    end
    if bootstrap_schema.jsi_ptr == jsi_ptr
      # this is the metaschema (it is described by itself)
      extend Metaschema
    end
  end

  @jsi_schemas = SchemaSet.new(our_bootstrap_schemas) do |bootstrap_schema|
    if bootstrap_schema.jsi_ptr == jsi_ptr
      self
    elsif bootstrap_schema.jsi_ptr.root?
      @jsi_root_node
    else
      new_node(
        jsi_ptr: bootstrap_schema.jsi_ptr,
        jsi_schema_base_uri: bootstrap_schema.jsi_schema_base_uri,
        jsi_root_node: @jsi_root_node,
      )
    end
  end

  # note: jsi_schemas must already be set for jsi_schema_module to be used/extended
  if is_a?(Metaschema)
    describes_schema!(schema_implementation_modules)
  end

  @jsi_schemas.each do |schema|
    extend schema.jsi_schema_module
  end

  # workarounds
  begin # draft 4 boolean schema workaround
    # in draft 4, boolean schemas are not described in the root, but on anyOf schemas on
    # properties/additionalProperties and properties/additionalItems.
    # these still describe schemas, despite not being described by the metaschema.
    addtlPropsanyOf = metaschema_root_ptr["properties"]["additionalProperties"]["anyOf"]
    addtlItemsanyOf = metaschema_root_ptr["properties"]["additionalItems"]["anyOf"]

    if !jsi_ptr.root? && [addtlPropsanyOf, addtlItemsanyOf].include?(jsi_ptr.parent)
      describes_schema!(schema_implementation_modules)
    end
  end
end

Instance Attribute Details

#jsi_schemasJSI::SchemaSet (readonly)

JSI Schemas describing this MetaschemaNode

Returns:



146
147
148
# File 'lib/jsi/metaschema_node.rb', line 146

def jsi_schemas
  @jsi_schemas
end

#metaschema_root_ptrJSI::Ptr (readonly)

ptr to the root of the metaschema in the jsi_document

Returns:



138
139
140
# File 'lib/jsi/metaschema_node.rb', line 138

def metaschema_root_ptr
  @metaschema_root_ptr
end

#root_schema_ptrJSI::Ptr (readonly)

ptr to the schema of the root of the jsi_document

Returns:



142
143
144
# File 'lib/jsi/metaschema_node.rb', line 142

def root_schema_ptr
  @root_schema_ptr
end

#schema_implementation_modulesSet<Module> (readonly)

Set of modules to apply to schemas which are instances of (described by) the metaschema

Returns:

  • (Set<Module>)


134
135
136
# File 'lib/jsi/metaschema_node.rb', line 134

def schema_implementation_modules
  @schema_implementation_modules
end

Instance Method Details

#[](token, as_jsi: :auto) ⇒ JSI::Base, Object

subscripts to return a child value identified by the given token.

Parameters:

  • token (String, Integer, Object)

    an array index or hash key (JSON object property name) of the instance identifying the child value

  • as_jsi (:auto, true, false) (defaults to: :auto)

    whether to return the result value as a JSI. one of:

    • :auto (default): by default a JSI will be returned when either:

      • the result is a complex value (responds to #to_ary or #to_hash)
      • the result is a schema (including true/false schemas)

    a plain value is returned when no schemas are known to describe the instance, or when the value is a simple type (anything unresponsive to #to_ary / #to_hash).

    • true: the result value will always be returned as a JSI. the #jsi_schemas of the result may be empty if no schemas describe the instance.
    • false: the result value will always be the plain instance.

    note that nil is returned (regardless of as_jsi) when there is no value to return because the token is not a hash key or array index of the instance and no default value applies. (one exception is when this JSI's instance is a Hash with a default or default_proc, which has unspecified behavior.)

Returns:

  • (JSI::Base, Object)

    the child value identified by the subscript token



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/jsi/metaschema_node.rb', line 153

def [](token, as_jsi: :auto)
  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(NoMethodError, "cannot subscript (using token: #{token.inspect}) from content: #{jsi_node_content.pretty_inspect.chomp}")
  end

  begin
    if token_in_range
      value_node = jsi_subinstance_memos[token: token]

      jsi_subinstance_as_jsi(value, value_node.jsi_schemas, as_jsi) do
        value_node
      end
    else
      # I think I will not support Hash#default/#default_proc in this case.
      nil
    end
  end
end

#jsi_fingerprintObject

an opaque fingerprint of this MetaschemaNode for FingerprintHash



210
211
212
# File 'lib/jsi/metaschema_node.rb', line 210

def jsi_fingerprint
  {class: self.class, jsi_document: jsi_document}.merge(our_initialize_params)
end

#jsi_modified_copy {|Object| ... } ⇒ MetaschemaNode

instantiates a new MetaschemaNode whose instance is a modified copy of this MetaschemaNode's instance

Yields:

  • (Object)

    the node content of the instance. the block should result in a (nondestructively) modified copy of this.

Returns:



182
183
184
185
186
187
188
189
190
191
192
# File 'lib/jsi/metaschema_node.rb', line 182

def jsi_modified_copy(&block)
  if jsi_ptr.root?
    modified_document = jsi_ptr.modified_document_copy(jsi_document, &block)
    MetaschemaNode.new(modified_document, **our_initialize_params)
  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