jsi 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.yardopts +6 -1
- data/CHANGELOG.md +33 -0
- data/LICENSE.md +1 -1
- data/README.md +29 -23
- data/jsi.gemspec +29 -0
- data/lib/jsi/base/mutability.rb +44 -0
- data/lib/jsi/base/node.rb +348 -0
- data/lib/jsi/base.rb +497 -339
- data/lib/jsi/jsi_coder.rb +19 -17
- data/lib/jsi/metaschema_node/bootstrap_schema.rb +61 -26
- data/lib/jsi/metaschema_node.rb +161 -133
- data/lib/jsi/ptr.rb +80 -47
- data/lib/jsi/schema/application/child_application/contains.rb +11 -2
- data/lib/jsi/schema/application/child_application/draft04.rb +0 -1
- data/lib/jsi/schema/application/child_application/draft06.rb +0 -1
- data/lib/jsi/schema/application/child_application/draft07.rb +0 -1
- data/lib/jsi/schema/application/child_application/items.rb +3 -3
- data/lib/jsi/schema/application/child_application/properties.rb +3 -3
- data/lib/jsi/schema/application/child_application.rb +0 -27
- data/lib/jsi/schema/application/inplace_application/dependencies.rb +1 -1
- data/lib/jsi/schema/application/inplace_application/draft04.rb +0 -1
- data/lib/jsi/schema/application/inplace_application/draft06.rb +0 -1
- data/lib/jsi/schema/application/inplace_application/draft07.rb +0 -1
- data/lib/jsi/schema/application/inplace_application/ifthenelse.rb +3 -3
- data/lib/jsi/schema/application/inplace_application/ref.rb +2 -2
- data/lib/jsi/schema/application/inplace_application/someof.rb +26 -11
- data/lib/jsi/schema/application/inplace_application.rb +0 -32
- data/lib/jsi/schema/draft04.rb +0 -1
- data/lib/jsi/schema/draft06.rb +0 -1
- data/lib/jsi/schema/draft07.rb +0 -1
- data/lib/jsi/schema/ref.rb +46 -19
- data/lib/jsi/schema/schema_ancestor_node.rb +69 -66
- data/lib/jsi/schema/validation/array.rb +3 -3
- data/lib/jsi/schema/validation/const.rb +1 -1
- data/lib/jsi/schema/validation/contains.rb +2 -2
- data/lib/jsi/schema/validation/dependencies.rb +1 -1
- data/lib/jsi/schema/validation/draft04/minmax.rb +8 -6
- data/lib/jsi/schema/validation/draft04.rb +0 -2
- data/lib/jsi/schema/validation/draft06.rb +0 -2
- data/lib/jsi/schema/validation/draft07.rb +0 -2
- data/lib/jsi/schema/validation/enum.rb +1 -1
- data/lib/jsi/schema/validation/ifthenelse.rb +5 -5
- data/lib/jsi/schema/validation/items.rb +7 -7
- data/lib/jsi/schema/validation/not.rb +1 -1
- data/lib/jsi/schema/validation/numeric.rb +5 -5
- data/lib/jsi/schema/validation/object.rb +2 -2
- data/lib/jsi/schema/validation/pattern.rb +2 -2
- data/lib/jsi/schema/validation/properties.rb +7 -7
- data/lib/jsi/schema/validation/property_names.rb +1 -1
- data/lib/jsi/schema/validation/ref.rb +2 -2
- data/lib/jsi/schema/validation/required.rb +1 -1
- data/lib/jsi/schema/validation/someof.rb +3 -3
- data/lib/jsi/schema/validation/string.rb +2 -2
- data/lib/jsi/schema/validation/type.rb +1 -1
- data/lib/jsi/schema/validation.rb +1 -3
- data/lib/jsi/schema.rb +443 -226
- data/lib/jsi/schema_classes.rb +241 -147
- data/lib/jsi/schema_registry.rb +78 -19
- data/lib/jsi/schema_set.rb +114 -28
- data/lib/jsi/simple_wrap.rb +18 -4
- data/lib/jsi/util/private/attr_struct.rb +141 -0
- data/lib/jsi/util/private/memo_map.rb +75 -0
- data/lib/jsi/util/private.rb +185 -0
- data/lib/jsi/{typelike_modules.rb → util/typelike.rb} +79 -105
- data/lib/jsi/util.rb +157 -153
- data/lib/jsi/validation/error.rb +4 -0
- data/lib/jsi/validation/result.rb +18 -32
- data/lib/jsi/version.rb +1 -1
- data/lib/jsi.rb +65 -39
- data/lib/schemas/json-schema.org/draft-04/schema.rb +160 -3
- data/lib/schemas/json-schema.org/draft-06/schema.rb +162 -3
- data/lib/schemas/json-schema.org/draft-07/schema.rb +189 -3
- metadata +27 -11
- data/lib/jsi/metaschema.rb +0 -7
- data/lib/jsi/pathed_node.rb +0 -116
- data/lib/jsi/schema/validation/core.rb +0 -39
- data/lib/jsi/util/attr_struct.rb +0 -106
data/lib/jsi/base.rb
CHANGED
@@ -1,47 +1,35 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module JSI
|
4
|
-
# JSI::Base
|
4
|
+
# A JSI::Base instance represents a node in a JSON document (its {#jsi_document}) at a particular
|
5
|
+
# location (its {#jsi_ptr}), described by any number of JSON Schemas (its {#jsi_schemas}).
|
5
6
|
#
|
6
|
-
#
|
7
|
-
#
|
7
|
+
# JSI::Base is an abstract base class. The subclasses used to instantiate JSIs are dynamically created as
|
8
|
+
# needed for a given instance.
|
8
9
|
#
|
9
|
-
#
|
10
|
+
# These subclasses are generally intended to be ignored by applications using this library - the purpose
|
11
|
+
# they serve is to include modules relevant to the instance. The modules these classes include are:
|
10
12
|
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
# the JSI::Base class itself is not intended to be instantiated.
|
13
|
+
# - the {Schema#jsi_schema_module} of each schema which describes the instance
|
14
|
+
# - {Base::HashNode}, {Base::ArrayNode}, or {Base::StringNode} if the instance is
|
15
|
+
# a hash/object, array, or string
|
16
|
+
# - Modules defining accessor methods for property names described by the schemas
|
16
17
|
class Base
|
17
|
-
|
18
|
-
|
19
|
-
|
18
|
+
autoload :ArrayNode, 'jsi/base/node'
|
19
|
+
autoload :HashNode, 'jsi/base/node'
|
20
|
+
autoload :StringNode, 'jsi/base/node'
|
21
|
+
autoload(:Mutable, 'jsi/base/mutability')
|
22
|
+
autoload(:Immutable, 'jsi/base/mutability')
|
20
23
|
|
21
|
-
|
22
|
-
# the class than to conditionally extend the instance.
|
23
|
-
include Enumerable
|
24
|
+
include Schema::SchemaAncestorNode
|
24
25
|
|
25
|
-
#
|
26
|
-
|
26
|
+
# An exception raised when attempting to access a child of a node which cannot have children.
|
27
|
+
# A complex node can have children, a simple node cannot.
|
28
|
+
class SimpleNodeChildError < StandardError
|
27
29
|
end
|
28
30
|
|
29
31
|
class << self
|
30
|
-
#
|
31
|
-
def new_jsi(instance, **kw, &b)
|
32
|
-
new(instance, **kw, &b)
|
33
|
-
end
|
34
|
-
|
35
|
-
# @private
|
36
|
-
# is the constant JSI::SchemaClasses::<self.schema_classes_const_name> defined?
|
37
|
-
# (if so, we will prefer to use something more human-readable than that ugly mess.)
|
38
|
-
def in_schema_classes
|
39
|
-
# #name sets @in_schema_classes
|
40
|
-
name
|
41
|
-
@in_schema_classes
|
42
|
-
end
|
43
|
-
|
44
|
-
# a string indicating a class name if one is defined, as well as the schema module name
|
32
|
+
# A string indicating the schema module name
|
45
33
|
# and/or schema URI of each schema the class represents.
|
46
34
|
# @return [String]
|
47
35
|
def inspect
|
@@ -49,11 +37,11 @@ module JSI
|
|
49
37
|
super
|
50
38
|
else
|
51
39
|
schema_names = jsi_class_schemas.map do |schema|
|
52
|
-
|
53
|
-
if
|
54
|
-
"#{
|
55
|
-
elsif
|
56
|
-
|
40
|
+
mod_name = schema.jsi_schema_module.name_from_ancestor
|
41
|
+
if mod_name && schema.schema_absolute_uri
|
42
|
+
"#{mod_name} <#{schema.schema_absolute_uri}>"
|
43
|
+
elsif mod_name
|
44
|
+
mod_name
|
57
45
|
elsif schema.schema_uri
|
58
46
|
schema.schema_uri.to_s
|
59
47
|
else
|
@@ -61,141 +49,109 @@ module JSI
|
|
61
49
|
end
|
62
50
|
end
|
63
51
|
|
64
|
-
if
|
65
|
-
|
66
|
-
"#{name} (0 schemas)"
|
67
|
-
else
|
68
|
-
"#{name} (#{schema_names.join(', ')})"
|
69
|
-
end
|
52
|
+
if schema_names.empty?
|
53
|
+
"(JSI Schema Class for 0 schemas#{jsi_class_includes.map { |n| " + #{n}" }})"
|
70
54
|
else
|
71
|
-
|
72
|
-
"(JSI Schema Class for 0 schemas)"
|
73
|
-
else
|
74
|
-
"(JSI Schema Class: #{schema_names.join(', ')})"
|
75
|
-
end
|
55
|
+
-"(JSI Schema Class: #{(schema_names + jsi_class_includes.map(&:name)).join(' + ')})"
|
76
56
|
end
|
77
57
|
end
|
78
58
|
end
|
79
59
|
|
80
|
-
|
81
|
-
|
82
|
-
# @private
|
83
|
-
# see {.name}
|
84
|
-
def schema_classes_const_name
|
85
|
-
if respond_to?(:jsi_class_schemas)
|
86
|
-
schema_names = jsi_class_schemas.map do |schema|
|
87
|
-
if schema.jsi_schema_module.name
|
88
|
-
schema.jsi_schema_module.name
|
89
|
-
elsif schema.schema_uri
|
90
|
-
schema.schema_uri.to_s
|
91
|
-
else
|
92
|
-
nil
|
93
|
-
end
|
94
|
-
end
|
95
|
-
if !schema_names.any?(&:nil?) && !schema_names.empty?
|
96
|
-
schema_names.sort.map { |n| 'X' + n.to_s.gsub(/[^\w]/, '_') }.join('')
|
97
|
-
end
|
98
|
-
end
|
60
|
+
def to_s
|
61
|
+
inspect
|
99
62
|
end
|
100
63
|
|
101
|
-
#
|
102
|
-
# this class represents
|
64
|
+
# A constant name of this class. This is generated from any schema module name or URI of each schema
|
65
|
+
# this class represents, or random characters.
|
103
66
|
#
|
104
67
|
# this generated name is not too pretty but can be more helpful than an anonymous class, especially
|
105
68
|
# in error messages.
|
106
69
|
#
|
107
70
|
# @return [String]
|
108
71
|
def name
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
72
|
+
return super if instance_variable_defined?(:@tried_to_name)
|
73
|
+
@tried_to_name = true
|
74
|
+
return super unless respond_to?(:jsi_class_schemas)
|
75
|
+
alnum = proc { |id| (id % 36**4).to_s(36).rjust(4, '0').upcase }
|
76
|
+
schema_names = jsi_class_schemas.map do |schema|
|
77
|
+
named_ancestor_schema, tokens = schema.jsi_schema_module.send(:named_ancestor_schema_tokens)
|
78
|
+
if named_ancestor_schema
|
79
|
+
[named_ancestor_schema.jsi_schema_module.name, *tokens].join('_')
|
80
|
+
elsif schema.schema_uri
|
81
|
+
schema.schema_uri.to_s
|
113
82
|
else
|
114
|
-
|
115
|
-
@in_schema_classes = true
|
83
|
+
[alnum[schema.jsi_root_node.__id__], *schema.jsi_ptr.tokens].join('_')
|
116
84
|
end
|
117
85
|
end
|
86
|
+
includes_names = jsi_class_includes.map { |m| m.name.sub(/\AJSI::Base::/, '').gsub(Util::RUBY_REJECT_NAME_RE, '_') }
|
87
|
+
if schema_names.any?
|
88
|
+
parts = schema_names.compact.sort.map { |n| 'X' + n.to_s }
|
89
|
+
parts += includes_names
|
90
|
+
const_name = Util.const_name_from_parts(parts, join: '__')
|
91
|
+
const_name += "__" + alnum[__id__] if SchemaClasses.const_defined?(const_name)
|
92
|
+
else
|
93
|
+
const_name = (['X' + alnum[__id__]] + includes_names).join('__')
|
94
|
+
end
|
95
|
+
# collisions are technically possible though vanishingly unlikely
|
96
|
+
SchemaClasses.const_set(const_name, self) unless SchemaClasses.const_defined?(const_name)
|
118
97
|
super
|
119
98
|
end
|
120
99
|
end
|
121
100
|
|
122
|
-
#
|
123
|
-
# from a document and pointer.
|
101
|
+
# initializes a JSI whose instance is in the given document at the given pointer.
|
124
102
|
#
|
125
|
-
#
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
#
|
131
|
-
#
|
132
|
-
#
|
133
|
-
#
|
134
|
-
# @param instance [Object] the JSON Schema instance to be represented as a JSI
|
135
|
-
# @param jsi_document [Object] for internal use. the instance may be specified as a
|
136
|
-
# node in the `jsi_document` param, pointed to by `jsi_ptr`. the param `instance`
|
137
|
-
# MUST be `NOINSTANCE` to use the jsi_document + jsi_ptr form. `jsi_document` MUST
|
138
|
-
# NOT be passed if `instance` is anything other than `NOINSTANCE`.
|
139
|
-
# @param jsi_ptr [JSI::Ptr] for internal use. a pointer specifying
|
140
|
-
# the path of this instance in the `jsi_document` param. `jsi_ptr` must be passed
|
141
|
-
# iff `jsi_document` is passed, i.e. when `instance` is `NOINSTANCE`
|
142
|
-
# @param jsi_root_node [JSI::Base] for internal use, specifies the JSI at the root of the document
|
103
|
+
# this is a private api - users should look elsewhere to instantiate JSIs, in particular:
|
104
|
+
#
|
105
|
+
# - {JSI.new_schema} and {Schema::MetaSchema#new_schema} to instantiate schemas
|
106
|
+
# - {Schema#new_jsi} to instantiate schema instances
|
107
|
+
#
|
108
|
+
# @api private
|
109
|
+
# @param jsi_document [Object] the document containing the instance
|
110
|
+
# @param jsi_ptr [JSI::Ptr] a pointer pointing to the JSI's instance in the document
|
143
111
|
# @param jsi_schema_base_uri [Addressable::URI] see {SchemaSet#new_jsi} param uri
|
144
|
-
# @param jsi_schema_resource_ancestors [Array<JSI::Base>]
|
145
|
-
|
146
|
-
|
147
|
-
jsi_ptr:
|
148
|
-
|
112
|
+
# @param jsi_schema_resource_ancestors [Array<JSI::Base + JSI::Schema>]
|
113
|
+
# @param jsi_root_node [JSI::Base] the JSI of the root of the document containing this JSI
|
114
|
+
def initialize(jsi_document,
|
115
|
+
jsi_ptr: Ptr[],
|
116
|
+
jsi_indicated_schemas: ,
|
149
117
|
jsi_schema_base_uri: nil,
|
150
|
-
jsi_schema_resource_ancestors:
|
118
|
+
jsi_schema_resource_ancestors: Util::EMPTY_ARY,
|
119
|
+
jsi_schema_registry: ,
|
120
|
+
jsi_content_to_immutable: ,
|
121
|
+
jsi_root_node: nil
|
151
122
|
)
|
152
|
-
unless respond_to?(:jsi_schemas)
|
153
|
-
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.")
|
154
|
-
end
|
123
|
+
#chkbug raise(Bug, "no #jsi_schemas") unless respond_to?(:jsi_schemas)
|
155
124
|
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
self.jsi_document = jsi_document
|
166
|
-
self.jsi_ptr = jsi_ptr
|
167
|
-
if @jsi_ptr.root?
|
168
|
-
raise(Bug, "jsi_root_node cannot be specified for root JSI") if jsi_root_node
|
169
|
-
@jsi_root_node = self
|
170
|
-
else
|
171
|
-
if !jsi_root_node.is_a?(JSI::Base)
|
172
|
-
raise(TypeError, "jsi_root_node must be a JSI::Base; got: #{jsi_root_node.inspect}")
|
173
|
-
end
|
174
|
-
if !jsi_root_node.jsi_ptr.root?
|
175
|
-
raise(Bug, "jsi_root_node ptr #{jsi_root_node.jsi_ptr.inspect} is not root")
|
176
|
-
end
|
177
|
-
@jsi_root_node = jsi_root_node
|
178
|
-
end
|
179
|
-
else
|
180
|
-
raise(Bug, 'incorrect usage') if jsi_document || jsi_ptr || jsi_root_node
|
181
|
-
@jsi_document = instance
|
182
|
-
@jsi_ptr = Ptr[]
|
125
|
+
self.jsi_document = jsi_document
|
126
|
+
self.jsi_ptr = jsi_ptr
|
127
|
+
self.jsi_indicated_schemas = jsi_indicated_schemas
|
128
|
+
self.jsi_schema_base_uri = jsi_schema_base_uri
|
129
|
+
self.jsi_schema_resource_ancestors = jsi_schema_resource_ancestors
|
130
|
+
self.jsi_schema_registry = jsi_schema_registry
|
131
|
+
@jsi_content_to_immutable = jsi_content_to_immutable
|
132
|
+
if @jsi_ptr.root?
|
133
|
+
#chkbug raise(Bug, "jsi_root_node specified for root JSI") if jsi_root_node
|
183
134
|
@jsi_root_node = self
|
135
|
+
else
|
136
|
+
#chkbug raise(Bug, "jsi_root_node is not JSI::Base") if !jsi_root_node.is_a?(JSI::Base)
|
137
|
+
#chkbug raise(Bug, "jsi_root_node ptr is not root") if !jsi_root_node.jsi_ptr.root?
|
138
|
+
@jsi_root_node = jsi_root_node
|
184
139
|
end
|
185
140
|
|
186
|
-
|
187
|
-
|
141
|
+
jsi_memomaps_initialize
|
142
|
+
jsi_mutability_initialize
|
188
143
|
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
extend PathedArrayNode
|
144
|
+
super()
|
145
|
+
|
146
|
+
if jsi_instance.is_a?(JSI::Base)
|
147
|
+
raise(TypeError, "a JSI::Base instance must not be another JSI::Base. received: #{jsi_instance.pretty_inspect.chomp}")
|
194
148
|
end
|
195
149
|
end
|
196
150
|
|
197
151
|
# @!method jsi_schemas
|
198
|
-
#
|
152
|
+
# The set of schemas that describe this instance.
|
153
|
+
# These are the applicator schemas that apply to this instance, the result of inplace application
|
154
|
+
# of our {#jsi_indicated_schemas}.
|
199
155
|
# @return [JSI::SchemaSet]
|
200
156
|
# note: defined on subclasses by JSI::SchemaClasses.class_for_schemas
|
201
157
|
|
@@ -207,66 +163,90 @@ module JSI
|
|
207
163
|
# @return [JSI::Ptr]
|
208
164
|
attr_reader :jsi_ptr
|
209
165
|
|
166
|
+
# Comes from the param `to_immutable` of {SchemaSet#new_jsi} (or other `new_jsi` /
|
167
|
+
# `new_schema` / `new_schema_module` method).
|
168
|
+
# Immutable JSIs use this when instantiating a modified copy so its instance is also immutable.
|
169
|
+
# @return [#call, nil]
|
170
|
+
attr_reader(:jsi_content_to_immutable)
|
171
|
+
|
210
172
|
# the JSI at the root of this JSI's document
|
211
173
|
# @return [JSI::Base]
|
212
174
|
attr_reader :jsi_root_node
|
213
175
|
|
214
|
-
# the
|
215
|
-
|
176
|
+
# the content of this node in our {#jsi_document} at our {#jsi_ptr}. the same as {#jsi_instance}.
|
177
|
+
def jsi_node_content
|
178
|
+
# stub method for doc, overridden by Mutable/Immutable
|
179
|
+
end
|
216
180
|
|
217
|
-
#
|
218
|
-
#
|
219
|
-
|
220
|
-
|
181
|
+
# The JSON schema instance this JSI represents - the underlying JSON data used to instantiate this JSI.
|
182
|
+
# The same as {#jsi_node_content} - 'node content' is usually preferable terminology, to avoid
|
183
|
+
# ambiguity in the heavily overloaded term 'instance'.
|
184
|
+
def jsi_instance
|
185
|
+
jsi_node_content
|
221
186
|
end
|
222
187
|
|
223
|
-
#
|
188
|
+
# the schemas indicated as describing this instance, prior to inplace application.
|
224
189
|
#
|
225
|
-
#
|
190
|
+
# this is different from {#jsi_schemas}, which are the inplace applicator schemas
|
191
|
+
# which describe this instance. for most purposes, `#jsi_schemas` is more relevant.
|
226
192
|
#
|
227
|
-
#
|
193
|
+
# `jsi_indicated_schemas` does not include inplace applicator schemas, such as the
|
194
|
+
# subschemas of `allOf`, whereas `#jsi_schemas` does.
|
195
|
+
#
|
196
|
+
# this does include indicated schemas which do not apply themselves, such as `$ref`
|
197
|
+
# schemas (on json schema drafts up to 7) - these are not included on `#jsi_schemas`.
|
198
|
+
#
|
199
|
+
# @return [JSI::SchemaSet]
|
200
|
+
attr_reader :jsi_indicated_schemas
|
201
|
+
|
202
|
+
# yields a JSI of each node at or below this one in this JSI's document.
|
203
|
+
#
|
204
|
+
# @param propertyNames [Boolean] Whether to also yield each object property
|
205
|
+
# name (Hash key) of any descendent which is a hash/object.
|
206
|
+
# These are described by `propertyNames` subshemas of that object's schemas.
|
207
|
+
# They are not actual descendents of this node.
|
208
|
+
# See {HashNode#jsi_each_propertyName}.
|
209
|
+
# @yield [JSI::Base] each descendent node, starting with self
|
228
210
|
# @return [nil, Enumerator] an Enumerator if invoked without a block; otherwise nil
|
229
|
-
def
|
230
|
-
return to_enum(__method__) unless block
|
211
|
+
def jsi_each_descendent_node(propertyNames: false, &block)
|
212
|
+
return to_enum(__method__, propertyNames: propertyNames) unless block
|
231
213
|
|
232
214
|
yield self
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
elsif respond_to?(:to_ary)
|
238
|
-
each_index do |i|
|
239
|
-
self[i, as_jsi: true].jsi_each_child_node(&block)
|
215
|
+
|
216
|
+
if propertyNames && is_a?(HashNode)
|
217
|
+
jsi_each_propertyName do |propertyName|
|
218
|
+
propertyName.jsi_each_descendent_node(propertyNames: propertyNames, &block)
|
240
219
|
end
|
241
220
|
end
|
221
|
+
|
222
|
+
jsi_each_child_token do |token|
|
223
|
+
jsi_child_node(token).jsi_each_descendent_node(propertyNames: propertyNames, &block)
|
224
|
+
end
|
225
|
+
|
242
226
|
nil
|
243
227
|
end
|
244
228
|
|
245
|
-
# recursively selects
|
246
|
-
#
|
229
|
+
# recursively selects descendent nodes of this JSI, returning a modified copy of self containing only
|
230
|
+
# descendent nodes for which the given block had a true-ish result.
|
247
231
|
#
|
248
232
|
# this method yields a node before recursively descending to its child nodes, so leaf nodes are yielded
|
249
|
-
# last, after their parents. if a node is not selected, its
|
233
|
+
# last, after their parents. if a node is not selected, its descendents are never recursed.
|
250
234
|
#
|
251
|
-
# @yield [JSI::Base] each
|
235
|
+
# @yield [JSI::Base] each descendent node below self
|
252
236
|
# @return [JSI::Base] modified copy of self containing only the selected nodes
|
253
|
-
def
|
237
|
+
def jsi_select_descendents_node_first(&block)
|
254
238
|
jsi_modified_copy do |instance|
|
255
|
-
if
|
239
|
+
if jsi_array? || jsi_hash?
|
256
240
|
res = instance.class.new
|
257
|
-
|
258
|
-
v =
|
241
|
+
jsi_each_child_token do |token|
|
242
|
+
v = jsi_child_node(token)
|
259
243
|
if yield(v)
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
each_index do |i|
|
267
|
-
e = self[i, as_jsi: true]
|
268
|
-
if yield(e)
|
269
|
-
res << e.jsi_select_children_node_first(&block).jsi_node_content
|
244
|
+
res_v = v.jsi_select_descendents_node_first(&block).jsi_node_content
|
245
|
+
if jsi_array?
|
246
|
+
res << res_v
|
247
|
+
else
|
248
|
+
res[token] = res_v
|
249
|
+
end
|
270
250
|
end
|
271
251
|
end
|
272
252
|
res
|
@@ -276,31 +256,27 @@ module JSI
|
|
276
256
|
end
|
277
257
|
end
|
278
258
|
|
279
|
-
# recursively selects
|
280
|
-
#
|
259
|
+
# recursively selects descendent nodes of this JSI, returning a modified copy of self containing only
|
260
|
+
# descendent nodes for which the given block had a true-ish result.
|
281
261
|
#
|
282
262
|
# this method recursively descends child nodes before yielding each node, so leaf nodes are yielded
|
283
263
|
# before their parents.
|
284
264
|
#
|
285
|
-
# @yield [JSI::Base] each
|
265
|
+
# @yield [JSI::Base] each descendent node below self
|
286
266
|
# @return [JSI::Base] modified copy of self containing only the selected nodes
|
287
|
-
def
|
267
|
+
def jsi_select_descendents_leaf_first(&block)
|
288
268
|
jsi_modified_copy do |instance|
|
289
|
-
if
|
269
|
+
if jsi_array? || jsi_hash?
|
290
270
|
res = instance.class.new
|
291
|
-
|
292
|
-
v =
|
271
|
+
jsi_each_child_token do |token|
|
272
|
+
v = jsi_child_node(token).jsi_select_descendents_leaf_first(&block)
|
293
273
|
if yield(v)
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
each_index do |i|
|
301
|
-
e = self[i, as_jsi: true].jsi_select_children_leaf_first(&block)
|
302
|
-
if yield(e)
|
303
|
-
res << e.jsi_node_content
|
274
|
+
res_v = v.jsi_node_content
|
275
|
+
if jsi_array?
|
276
|
+
res << res_v
|
277
|
+
else
|
278
|
+
res[token] = res_v
|
279
|
+
end
|
304
280
|
end
|
305
281
|
end
|
306
282
|
res
|
@@ -318,42 +294,188 @@ module JSI
|
|
318
294
|
|
319
295
|
jsi_ptr.tokens.map do |token|
|
320
296
|
parent.tap do
|
321
|
-
parent = parent
|
297
|
+
parent = parent.jsi_child_node(token)
|
322
298
|
end
|
323
|
-
end.reverse
|
299
|
+
end.reverse!.freeze
|
324
300
|
end
|
325
301
|
|
326
302
|
# the immediate parent of this JSI. nil if there is no parent.
|
327
303
|
#
|
328
304
|
# @return [JSI::Base, nil]
|
329
305
|
def jsi_parent_node
|
330
|
-
|
306
|
+
jsi_ptr.root? ? nil : jsi_root_node.jsi_descendent_node(jsi_ptr.parent)
|
307
|
+
end
|
308
|
+
|
309
|
+
# ancestor JSI instances from this node up to the root. this node itself is always its own first ancestor.
|
310
|
+
#
|
311
|
+
# @return [Array<JSI::Base>]
|
312
|
+
def jsi_ancestor_nodes
|
313
|
+
ancestors = []
|
314
|
+
ancestor = jsi_root_node
|
315
|
+
ancestors << ancestor
|
316
|
+
|
317
|
+
jsi_ptr.tokens.each do |token|
|
318
|
+
ancestor = ancestor.jsi_child_node(token)
|
319
|
+
ancestors << ancestor
|
320
|
+
end
|
321
|
+
ancestors.reverse!.freeze
|
322
|
+
end
|
323
|
+
|
324
|
+
# the descendent node at the given pointer
|
325
|
+
#
|
326
|
+
# @param ptr [JSI::Ptr, #to_ary]
|
327
|
+
# @return [JSI::Base]
|
328
|
+
def jsi_descendent_node(ptr)
|
329
|
+
descendent = Ptr.ary_ptr(ptr).evaluate(self, as_jsi: true)
|
330
|
+
descendent
|
331
|
+
end
|
332
|
+
|
333
|
+
# A shorthand alias for {#jsi_descendent_node}.
|
334
|
+
#
|
335
|
+
# Note that, though more convenient to type, using an operator whose meaning may not be intuitive
|
336
|
+
# to a reader could impair readability of code.
|
337
|
+
#
|
338
|
+
# examples:
|
339
|
+
#
|
340
|
+
# my_jsi / ['foo', 'bar']
|
341
|
+
# my_jsi / %w(foo bar)
|
342
|
+
# my_schema / JSI::Ptr['additionalProperties']
|
343
|
+
# my_schema / %w(properties foo items additionalProperties)
|
344
|
+
#
|
345
|
+
# @param (see #jsi_descendent_node)
|
346
|
+
# @return (see #jsi_descendent_node)
|
347
|
+
def /(ptr)
|
348
|
+
jsi_descendent_node(ptr)
|
349
|
+
end
|
350
|
+
|
351
|
+
# yields each token (array index or hash key) identifying a child node.
|
352
|
+
# yields nothing if this node is not complex or has no children.
|
353
|
+
#
|
354
|
+
# @yield [String, Integer] each child token
|
355
|
+
# @return [nil, Enumerator] an Enumerator if invoked without a block; otherwise nil
|
356
|
+
def jsi_each_child_token
|
357
|
+
# note: overridden by Base::HashNode, Base::ArrayNode
|
358
|
+
return to_enum(__method__) { 0 } unless block_given?
|
359
|
+
nil
|
360
|
+
end
|
361
|
+
|
362
|
+
# Does the given token identify a child of this node?
|
363
|
+
#
|
364
|
+
# In other words, is the given token an array index or hash key of the instance?
|
365
|
+
#
|
366
|
+
# Always false if this is not a complex node.
|
367
|
+
#
|
368
|
+
# @param token [String, Integer]
|
369
|
+
# @return [Boolean]
|
370
|
+
def jsi_child_token_in_range?(token)
|
371
|
+
# note: overridden by Base::HashNode, Base::ArrayNode
|
372
|
+
false
|
373
|
+
end
|
374
|
+
|
375
|
+
# The child of the {#jsi_node_content} identified by the given token,
|
376
|
+
# or `nil` if the token does not identify an existing child.
|
377
|
+
#
|
378
|
+
# In other words, the element of the instance array at the given index,
|
379
|
+
# or the value of the instance hash/object for the given key.
|
380
|
+
#
|
381
|
+
# @return [Object, nil]
|
382
|
+
# @raise [SimpleNodeChildError] if this node is not complex (its instance is not array or hash)
|
383
|
+
def jsi_node_content_child(token)
|
384
|
+
# note: overridden by Base::HashNode, Base::ArrayNode
|
385
|
+
jsi_simple_node_child_error(token)
|
386
|
+
end
|
387
|
+
|
388
|
+
# A child JSI node, or the child of our {#jsi_instance}, identified by the given token.
|
389
|
+
# The token must identify an existing child; behavior if the child does not exist is undefined.
|
390
|
+
#
|
391
|
+
# @param token (see Base#[])
|
392
|
+
# @param as_jsi (see Base#[])
|
393
|
+
# @return [JSI::Base, Object]
|
394
|
+
def jsi_child(token, as_jsi: )
|
395
|
+
child_content = jsi_node_content_child(token)
|
396
|
+
|
397
|
+
child_indicated_schemas = @child_indicated_schemas_map[token: token, content: jsi_node_content]
|
398
|
+
child_applied_schemas = @child_applied_schemas_map[token: token, child_indicated_schemas: child_indicated_schemas, child_content: child_content]
|
399
|
+
|
400
|
+
jsi_child_as_jsi(child_content, child_applied_schemas, as_jsi) do
|
401
|
+
@child_node_map[
|
402
|
+
token: token,
|
403
|
+
child_indicated_schemas: child_indicated_schemas,
|
404
|
+
child_applied_schemas: child_applied_schemas,
|
405
|
+
includes: SchemaClasses.includes_for(child_content),
|
406
|
+
]
|
407
|
+
end
|
331
408
|
end
|
409
|
+
private :jsi_child # internals for #[] but idk, could be public
|
410
|
+
|
411
|
+
# @param token (see Base#[])
|
412
|
+
# @return [JSI::Base]
|
413
|
+
protected def jsi_child_node(token)
|
414
|
+
jsi_child(token, as_jsi: true)
|
415
|
+
end
|
416
|
+
|
417
|
+
# A default value for a child of this node identified by the given token, if schemas describing
|
418
|
+
# that child define a default value.
|
419
|
+
#
|
420
|
+
# If no schema describes a default value for the child (or in the unusual case that multiple
|
421
|
+
# schemas define different defaults), the result is `nil`.
|
422
|
+
#
|
423
|
+
# See also the `use_default` param of {Base#[]}.
|
424
|
+
#
|
425
|
+
# @param token (see Base#[])
|
426
|
+
# @param as_jsi (see Base#[])
|
427
|
+
# @return [JSI::Base, nil]
|
428
|
+
def jsi_default_child(token, as_jsi: )
|
429
|
+
child_content = jsi_node_content_child(token)
|
430
|
+
|
431
|
+
child_indicated_schemas = @child_indicated_schemas_map[token: token, content: jsi_node_content]
|
432
|
+
child_applied_schemas = @child_applied_schemas_map[token: token, child_indicated_schemas: child_indicated_schemas, child_content: child_content]
|
433
|
+
|
434
|
+
defaults = Set.new
|
435
|
+
child_applied_schemas.each do |child_schema|
|
436
|
+
if child_schema.keyword?('default')
|
437
|
+
defaults << child_schema.jsi_node_content['default']
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
if defaults.size == 1
|
442
|
+
# use the default value
|
443
|
+
jsi_child_as_jsi(defaults.first, child_applied_schemas, as_jsi) do
|
444
|
+
jsi_modified_copy do |i|
|
445
|
+
i.dup.tap { |i_dup| i_dup[token] = defaults.first }
|
446
|
+
end.jsi_child_node(token)
|
447
|
+
end
|
448
|
+
else
|
449
|
+
child_content
|
450
|
+
end
|
451
|
+
end
|
452
|
+
private :jsi_default_child # internals for #[] but idk, could be public
|
332
453
|
|
333
454
|
# subscripts to return a child value identified by the given token.
|
334
455
|
#
|
335
456
|
# @param token [String, Integer, Object] an array index or hash key (JSON object property name)
|
336
457
|
# of the instance identifying the child value
|
337
|
-
# @param as_jsi [:auto, true, false]
|
458
|
+
# @param as_jsi [:auto, true, false] (default is `:auto`)
|
459
|
+
# Whether to return the child as a JSI. One of:
|
338
460
|
#
|
339
|
-
# -
|
461
|
+
# - `:auto`: By default a JSI will be returned when either:
|
340
462
|
#
|
341
|
-
# - the result is a complex value (responds to #to_ary or #to_hash)
|
463
|
+
# - the result is a complex value (responds to #to_ary or #to_hash)
|
342
464
|
# - the result is a schema (including true/false schemas)
|
343
465
|
#
|
344
|
-
#
|
345
|
-
# simple type (anything unresponsive to #to_ary / #to_hash).
|
466
|
+
# The plain content is returned when it is a simple type.
|
346
467
|
#
|
347
|
-
# - true: the result value will always be returned as a JSI. the #jsi_schemas of the result may be
|
348
|
-
# if no schemas describe the instance.
|
468
|
+
# - true: the result value will always be returned as a JSI. the {#jsi_schemas} of the result may be
|
469
|
+
# empty if no schemas describe the instance.
|
349
470
|
# - false: the result value will always be the plain instance.
|
350
471
|
#
|
351
472
|
# note that nil is returned (regardless of as_jsi) when there is no value to return because the token
|
352
473
|
# is not a hash key or array index of the instance and no default value applies.
|
353
474
|
# (one exception is when this JSI's instance is a Hash with a default or default_proc, which has
|
354
475
|
# unspecified behavior.)
|
355
|
-
# @param use_default [true, false]
|
356
|
-
#
|
476
|
+
# @param use_default [true, false] (default is `false`)
|
477
|
+
# Whether to return a schema default value when the token refers to a child that is not in the document.
|
478
|
+
# If the token is not an array index or hash key of the instance, and one schema for the child
|
357
479
|
# instance specifies a default value, that default is returned.
|
358
480
|
#
|
359
481
|
# if the result with the default value is a JSI (per the `as_jsi` param), that JSI is not a child of
|
@@ -364,48 +486,23 @@ module JSI
|
|
364
486
|
# defaults are specified across those schemas), nil is returned.
|
365
487
|
# (one exception is when this JSI's instance is a Hash with a default or default_proc, which has
|
366
488
|
# unspecified behavior.)
|
367
|
-
# @return [JSI::Base, Object] the child value identified by the subscript token
|
368
|
-
def [](token, as_jsi:
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
elsif respond_to?(:to_ary)
|
373
|
-
token_in_range = jsi_node_content_ary_pubsend(:each_index).include?(token)
|
374
|
-
value = jsi_node_content_ary_pubsend(:[], token)
|
375
|
-
else
|
376
|
-
raise(CannotSubscriptError, "cannot subscript (using token: #{token.inspect}) from instance: #{jsi_instance.pretty_inspect.chomp}")
|
377
|
-
end
|
378
|
-
|
379
|
-
begin
|
380
|
-
subinstance_schemas = jsi_subinstance_schemas_memos[token: token, instance: jsi_node_content, subinstance: value]
|
489
|
+
# @return [JSI::Base, Object, nil] the child value identified by the subscript token
|
490
|
+
def [](token, as_jsi: jsi_child_as_jsi_default, use_default: jsi_child_use_default_default)
|
491
|
+
# note: overridden by Base::HashNode, Base::ArrayNode
|
492
|
+
jsi_simple_node_child_error(token)
|
493
|
+
end
|
381
494
|
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
if use_default
|
388
|
-
defaults = Set.new
|
389
|
-
subinstance_schemas.each do |subinstance_schema|
|
390
|
-
if subinstance_schema.respond_to?(:to_hash) && subinstance_schema.key?('default')
|
391
|
-
defaults << subinstance_schema['default']
|
392
|
-
end
|
393
|
-
end
|
394
|
-
end
|
495
|
+
# The default value for the param `as_jsi` of {#[]}, controlling whether a child is returned as a JSI instance.
|
496
|
+
# @return [:auto, true, false] a valid value of the `as_jsi` param of {#[]}
|
497
|
+
def jsi_child_as_jsi_default
|
498
|
+
:auto
|
499
|
+
end
|
395
500
|
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
# I kind of want to just return nil here. the preferred mechanism for
|
402
|
-
# a JSI's default value should be its schema. but returning nil ignores
|
403
|
-
# any value returned by Hash#default/#default_proc. there's no compelling
|
404
|
-
# reason not to support both, so I'll return that.
|
405
|
-
value
|
406
|
-
end
|
407
|
-
end
|
408
|
-
end
|
501
|
+
# The default value for the param `use_default` of {#[]}, controlling whether a schema default value is
|
502
|
+
# returned when a token refers to a child that is not in the document.
|
503
|
+
# @return [true, false] a valid value of the `use_default` param of {#[]}
|
504
|
+
def jsi_child_use_default_default
|
505
|
+
false
|
409
506
|
end
|
410
507
|
|
411
508
|
# assigns the subscript of the instance identified by the given token to the given value.
|
@@ -414,8 +511,8 @@ module JSI
|
|
414
511
|
# @param token [String, Integer, Object] token identifying the subscript to assign
|
415
512
|
# @param value [JSI::Base, Object] the value to be assigned
|
416
513
|
def []=(token, value)
|
417
|
-
unless
|
418
|
-
|
514
|
+
unless jsi_array? || jsi_hash?
|
515
|
+
jsi_simple_node_child_error(token)
|
419
516
|
end
|
420
517
|
if value.is_a?(Base)
|
421
518
|
self[token] = value.jsi_instance
|
@@ -427,12 +524,32 @@ module JSI
|
|
427
524
|
# the set of JSI schema modules corresponding to the schemas that describe this JSI
|
428
525
|
# @return [Set<Module>]
|
429
526
|
def jsi_schema_modules
|
430
|
-
jsi_schemas.map(&:jsi_schema_module)
|
527
|
+
Util.ensure_module_set(jsi_schemas.map(&:jsi_schema_module))
|
528
|
+
end
|
529
|
+
|
530
|
+
# Is this JSI described by the given schema (or schema module)?
|
531
|
+
#
|
532
|
+
# @param schema [Schema, SchemaModule]
|
533
|
+
# @return [Boolean]
|
534
|
+
def described_by?(schema)
|
535
|
+
if schema.is_a?(Schema)
|
536
|
+
jsi_schemas.include?(schema)
|
537
|
+
elsif schema.is_a?(SchemaModule)
|
538
|
+
jsi_schema_modules.include?(schema)
|
539
|
+
else
|
540
|
+
raise(TypeError, "expected a Schema or Schema Module; got: #{schema.pretty_inspect.chomp}")
|
541
|
+
end
|
542
|
+
end
|
543
|
+
|
544
|
+
# Is this a JSI Schema?
|
545
|
+
# @return [Boolean]
|
546
|
+
def jsi_is_schema?
|
547
|
+
false
|
431
548
|
end
|
432
549
|
|
433
550
|
# yields the content of this JSI's instance. the block must result in
|
434
|
-
# a modified copy of the yielded instance (not
|
435
|
-
# which will be used to instantiate a new JSI with the modified content.
|
551
|
+
# a modified copy of the yielded instance (not modified in place, which would alter this JSI
|
552
|
+
# as well) which will be used to instantiate and return a new JSI with the modified content.
|
436
553
|
#
|
437
554
|
# the result may have different schemas which describe it than this JSI's schemas,
|
438
555
|
# if conditional applicator schemas apply differently to the modified instance.
|
@@ -441,48 +558,75 @@ module JSI
|
|
441
558
|
# in a nondestructively modified copy of this.
|
442
559
|
# @return [JSI::Base subclass] the modified copy of self
|
443
560
|
def jsi_modified_copy(&block)
|
444
|
-
if @jsi_ptr.root?
|
445
561
|
modified_document = @jsi_ptr.modified_document_copy(@jsi_document, &block)
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
562
|
+
modified_jsi_root_node = @jsi_root_node.jsi_indicated_schemas.new_jsi(modified_document,
|
563
|
+
uri: @jsi_root_node.jsi_schema_base_uri,
|
564
|
+
register: false, # default is already false but this is a place to be explicit
|
565
|
+
schema_registry: jsi_schema_registry,
|
566
|
+
mutable: jsi_mutable?,
|
567
|
+
to_immutable: jsi_content_to_immutable,
|
451
568
|
)
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
569
|
+
modified_jsi_root_node.jsi_descendent_node(@jsi_ptr)
|
570
|
+
end
|
571
|
+
|
572
|
+
# Is the instance an array?
|
573
|
+
#
|
574
|
+
# An array is typically an instance of the Array class but may be an object that supports
|
575
|
+
# [implicit conversion](https://docs.ruby-lang.org/en/master/implicit_conversion_rdoc.html)
|
576
|
+
# with a `#to_ary` method.
|
577
|
+
#
|
578
|
+
# @return [Boolean]
|
579
|
+
def jsi_array?
|
580
|
+
# note: overridden by Base::ArrayNode
|
581
|
+
false
|
582
|
+
end
|
583
|
+
|
584
|
+
# Is the instance a ruby Hash (JSON object)?
|
585
|
+
#
|
586
|
+
# This is typically an instance of the Hash class but may be an object that supports
|
587
|
+
# [implicit conversion](https://docs.ruby-lang.org/en/master/implicit_conversion_rdoc.html)
|
588
|
+
# with a `#to_hash` method.
|
589
|
+
#
|
590
|
+
# @return [Boolean]
|
591
|
+
def jsi_hash?
|
592
|
+
# note: overridden by Base::HashNode
|
593
|
+
false
|
594
|
+
end
|
595
|
+
|
596
|
+
# Is this JSI mutable?
|
597
|
+
# @return [Boolean]
|
598
|
+
def jsi_mutable?
|
599
|
+
# note: overridden by Base::Mutable / Immutable
|
458
600
|
end
|
459
601
|
|
460
602
|
# validates this JSI's instance against its schemas
|
461
603
|
#
|
462
604
|
# @return [JSI::Validation::FullResult]
|
463
605
|
def jsi_validate
|
464
|
-
|
606
|
+
jsi_indicated_schemas.instance_validate(self)
|
465
607
|
end
|
466
608
|
|
467
609
|
# whether this JSI's instance is valid against all of its schemas
|
468
610
|
# @return [Boolean]
|
469
611
|
def jsi_valid?
|
470
|
-
|
471
|
-
end
|
472
|
-
|
473
|
-
# @private
|
474
|
-
def fully_validate(errors_as_objects: false)
|
475
|
-
raise(NotImplementedError, "Base#fully_validate removed: see new validation interface Base#jsi_validate")
|
612
|
+
jsi_indicated_schemas.instance_valid?(self)
|
476
613
|
end
|
477
614
|
|
478
|
-
#
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
#
|
484
|
-
|
485
|
-
|
615
|
+
# queries this JSI using the [JMESPath Ruby](https://rubygems.org/gems/jmespath) gem.
|
616
|
+
# see [https://jmespath.org/](https://jmespath.org/) to learn the JMESPath query language.
|
617
|
+
#
|
618
|
+
# the JMESPath gem is not a dependency of JSI, so must be installed / added to your Gemfile to use.
|
619
|
+
# e.g. `gem 'jmespath', '~> 1.5'`. note that versions below 1.5 are not compatible with JSI.
|
620
|
+
#
|
621
|
+
# @param expression [String] a [JMESPath](https://jmespath.org/) expression
|
622
|
+
# @param runtime_options passed to [JMESPath.search](https://rubydoc.info/gems/jmespath/JMESPath#search-class_method),
|
623
|
+
# though no runtime_options are publicly documented or normally used.
|
624
|
+
# @return [Array, Object, nil] query results.
|
625
|
+
# see [JMESPath.search](https://rubydoc.info/gems/jmespath/JMESPath#search-class_method)
|
626
|
+
def jmespath_search(expression, **runtime_options)
|
627
|
+
Util.require_jmespath
|
628
|
+
|
629
|
+
JMESPath.search(expression, self, **runtime_options)
|
486
630
|
end
|
487
631
|
|
488
632
|
def dup
|
@@ -492,7 +636,11 @@ module JSI
|
|
492
636
|
# a string representing this JSI, indicating any named schemas and inspecting its instance
|
493
637
|
# @return [String]
|
494
638
|
def inspect
|
495
|
-
"\#<#{jsi_object_group_text.join(' ')} #{jsi_instance.inspect}>"
|
639
|
+
-"\#<#{jsi_object_group_text.join(' ')} #{jsi_instance.inspect}>"
|
640
|
+
end
|
641
|
+
|
642
|
+
def to_s
|
643
|
+
inspect
|
496
644
|
end
|
497
645
|
|
498
646
|
# pretty-prints a representation of this JSI to the given printer
|
@@ -500,11 +648,9 @@ module JSI
|
|
500
648
|
def pretty_print(q)
|
501
649
|
q.text '#<'
|
502
650
|
q.text jsi_object_group_text.join(' ')
|
503
|
-
q.
|
504
|
-
q.nest(2) {
|
651
|
+
q.group(2) {
|
505
652
|
q.breakable ' '
|
506
653
|
q.pp jsi_instance
|
507
|
-
}
|
508
654
|
}
|
509
655
|
q.breakable ''
|
510
656
|
q.text '>'
|
@@ -513,107 +659,119 @@ module JSI
|
|
513
659
|
# @private
|
514
660
|
# @return [Array<String>]
|
515
661
|
def jsi_object_group_text
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
if schema_module_names.empty?
|
522
|
-
class_name
|
523
|
-
else
|
524
|
-
"#{class_name} (#{schema_module_names.join(', ')})"
|
525
|
-
end
|
526
|
-
else
|
527
|
-
schema_names = jsi_schemas.map { |schema| schema.jsi_schema_module.name_from_ancestor || schema.schema_uri }.compact
|
528
|
-
if schema_names.empty?
|
529
|
-
"JSI"
|
530
|
-
else
|
531
|
-
"JSI (#{schema_names.join(', ')})"
|
532
|
-
end
|
533
|
-
end
|
662
|
+
schema_names = jsi_schemas.map { |schema| schema.jsi_schema_module.name_from_ancestor || schema.schema_uri }.compact
|
663
|
+
if schema_names.empty?
|
664
|
+
class_txt = "JSI"
|
665
|
+
else
|
666
|
+
class_txt = -"JSI (#{schema_names.join(', ')})"
|
534
667
|
end
|
535
668
|
|
536
|
-
if (is_a?(
|
669
|
+
if (is_a?(ArrayNode) || is_a?(HashNode)) && ![Array, Hash].include?(jsi_node_content.class)
|
537
670
|
if jsi_node_content.respond_to?(:jsi_object_group_text)
|
538
671
|
content_txt = jsi_node_content.jsi_object_group_text
|
539
672
|
else
|
540
|
-
content_txt =
|
673
|
+
content_txt = jsi_node_content.class.to_s
|
541
674
|
end
|
542
675
|
else
|
543
|
-
content_txt =
|
676
|
+
content_txt = nil
|
544
677
|
end
|
545
678
|
|
546
679
|
[
|
547
680
|
class_txt,
|
548
|
-
is_a?(
|
681
|
+
is_a?(Schema::MetaSchema) ? "Meta-Schema" : is_a?(Schema) ? "Schema" : nil,
|
549
682
|
*content_txt,
|
550
|
-
].compact
|
683
|
+
].compact.freeze
|
551
684
|
end
|
552
685
|
|
553
|
-
#
|
554
|
-
#
|
555
|
-
def as_json(
|
556
|
-
|
686
|
+
# A structure coerced to JSONifiable types from the instance content.
|
687
|
+
# Calls {Util.as_json} with the instance and any given options.
|
688
|
+
def as_json(options = {})
|
689
|
+
Util.as_json(jsi_instance, **options)
|
690
|
+
end
|
691
|
+
|
692
|
+
# A JSON encoded string of the instance content.
|
693
|
+
# Calls {Util.to_json} with the instance and any given options.
|
694
|
+
# @return [String]
|
695
|
+
def to_json(options = {})
|
696
|
+
Util.to_json(jsi_instance, **options)
|
557
697
|
end
|
558
698
|
|
559
|
-
#
|
699
|
+
# see {Util::Private::FingerprintHash}
|
700
|
+
# @api private
|
560
701
|
def jsi_fingerprint
|
561
702
|
{
|
562
|
-
class:
|
703
|
+
class: JSI::Base,
|
704
|
+
jsi_schemas: jsi_schemas,
|
563
705
|
jsi_document: jsi_document,
|
564
706
|
jsi_ptr: jsi_ptr,
|
565
707
|
# for instances in documents with schemas:
|
566
708
|
jsi_resource_ancestor_uri: jsi_resource_ancestor_uri,
|
567
|
-
#
|
568
|
-
|
569
|
-
}
|
709
|
+
# different registries mean references may resolve to different resources so must not be equal
|
710
|
+
jsi_schema_registry: jsi_schema_registry,
|
711
|
+
}.freeze
|
570
712
|
end
|
571
|
-
include Util::FingerprintHash
|
572
713
|
|
573
714
|
private
|
574
715
|
|
575
|
-
def
|
576
|
-
jsi_memomap(
|
577
|
-
|
578
|
-
|
579
|
-
schema.each_child_applicator_schema(token, instance) do |child_app_schema|
|
580
|
-
child_app_schema.each_inplace_applicator_schema(subinstance) do |child_inpl_app_schema|
|
581
|
-
schemas << child_inpl_app_schema
|
582
|
-
end
|
583
|
-
end
|
584
|
-
end
|
585
|
-
end
|
586
|
-
end
|
716
|
+
def jsi_memomaps_initialize
|
717
|
+
@child_indicated_schemas_map = jsi_memomap(key_by: proc { |i| i[:token] }, &method(:jsi_child_indicated_schemas_compute))
|
718
|
+
@child_applied_schemas_map = jsi_memomap(key_by: proc { |i| i[:token] }, &method(:jsi_child_applied_schemas_compute))
|
719
|
+
@child_node_map = jsi_memomap(key_by: proc { |i| i[:token] }, &method(:jsi_child_node_compute))
|
587
720
|
end
|
588
721
|
|
589
|
-
def
|
590
|
-
|
591
|
-
|
592
|
-
|
722
|
+
def jsi_indicated_schemas=(jsi_indicated_schemas)
|
723
|
+
#chkbug raise(Bug) unless jsi_indicated_schemas.is_a?(SchemaSet)
|
724
|
+
@jsi_indicated_schemas = jsi_indicated_schemas
|
725
|
+
end
|
726
|
+
|
727
|
+
def jsi_child_node_compute(token: , child_indicated_schemas: , child_applied_schemas: , includes: )
|
728
|
+
jsi_class = JSI::SchemaClasses.class_for_schemas(child_applied_schemas,
|
729
|
+
includes: includes,
|
730
|
+
mutable: jsi_mutable?,
|
731
|
+
)
|
732
|
+
jsi_class.new(@jsi_document,
|
593
733
|
jsi_ptr: @jsi_ptr[token],
|
594
|
-
|
734
|
+
jsi_indicated_schemas: child_indicated_schemas,
|
595
735
|
jsi_schema_base_uri: jsi_resource_ancestor_uri,
|
596
736
|
jsi_schema_resource_ancestors: is_a?(Schema) ? jsi_subschema_resource_ancestors : jsi_schema_resource_ancestors,
|
737
|
+
jsi_schema_registry: jsi_schema_registry,
|
738
|
+
jsi_content_to_immutable: @jsi_content_to_immutable,
|
739
|
+
jsi_root_node: @jsi_root_node,
|
597
740
|
)
|
598
|
-
end
|
599
741
|
end
|
600
742
|
|
601
|
-
def
|
602
|
-
|
603
|
-
|
743
|
+
def jsi_child_indicated_schemas_compute(token: , content: )
|
744
|
+
jsi_schemas.child_applicator_schemas(token, content)
|
745
|
+
end
|
746
|
+
|
747
|
+
def jsi_child_applied_schemas_compute(token: , child_indicated_schemas: , child_content: )
|
748
|
+
child_indicated_schemas.inplace_applicator_schemas(child_content)
|
749
|
+
end
|
750
|
+
|
751
|
+
def jsi_child_as_jsi(child_content, child_schemas, as_jsi)
|
752
|
+
if [true, false].include?(as_jsi)
|
753
|
+
child_as_jsi = as_jsi
|
604
754
|
elsif as_jsi == :auto
|
605
|
-
|
606
|
-
|
607
|
-
|
755
|
+
child_is_complex = child_content.respond_to?(:to_hash) || child_content.respond_to?(:to_ary)
|
756
|
+
child_is_schema = child_schemas.any?(&:describes_schema?)
|
757
|
+
child_as_jsi = child_is_complex || child_is_schema
|
608
758
|
else
|
609
759
|
raise(ArgumentError, "as_jsi must be one of: :auto, true, false")
|
610
760
|
end
|
611
761
|
|
612
|
-
if
|
762
|
+
if child_as_jsi
|
613
763
|
yield
|
614
764
|
else
|
615
|
-
|
765
|
+
child_content
|
616
766
|
end
|
617
767
|
end
|
768
|
+
|
769
|
+
def jsi_simple_node_child_error(token)
|
770
|
+
raise(SimpleNodeChildError, [
|
771
|
+
"cannot access a child of this JSI node because this node is not complex",
|
772
|
+
"using token: #{token.inspect}",
|
773
|
+
"instance: #{jsi_instance.pretty_inspect.chomp}",
|
774
|
+
].join("\n"))
|
775
|
+
end
|
618
776
|
end
|
619
777
|
end
|