jsi 0.2.0 → 0.6.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 +1 -1
- data/CHANGELOG.md +36 -0
- data/LICENSE.md +613 -0
- data/README.md +153 -52
- data/lib/jsi/base.rb +485 -338
- data/lib/jsi/jsi_coder.rb +24 -18
- data/lib/jsi/metaschema.rb +7 -0
- data/lib/jsi/metaschema_node/bootstrap_schema.rb +100 -0
- data/lib/jsi/metaschema_node.rb +245 -0
- data/lib/jsi/pathed_node.rb +49 -46
- data/lib/jsi/ptr.rb +292 -0
- data/lib/jsi/schema/application/child_application/contains.rb +16 -0
- data/lib/jsi/schema/application/child_application/draft04.rb +22 -0
- data/lib/jsi/schema/application/child_application/draft06.rb +29 -0
- data/lib/jsi/schema/application/child_application/draft07.rb +29 -0
- data/lib/jsi/schema/application/child_application/items.rb +18 -0
- data/lib/jsi/schema/application/child_application/properties.rb +25 -0
- data/lib/jsi/schema/application/child_application.rb +40 -0
- data/lib/jsi/schema/application/draft04.rb +8 -0
- data/lib/jsi/schema/application/draft06.rb +8 -0
- data/lib/jsi/schema/application/draft07.rb +8 -0
- data/lib/jsi/schema/application/inplace_application/dependencies.rb +28 -0
- data/lib/jsi/schema/application/inplace_application/draft04.rb +26 -0
- data/lib/jsi/schema/application/inplace_application/draft06.rb +27 -0
- data/lib/jsi/schema/application/inplace_application/draft07.rb +33 -0
- data/lib/jsi/schema/application/inplace_application/ifthenelse.rb +20 -0
- data/lib/jsi/schema/application/inplace_application/ref.rb +18 -0
- data/lib/jsi/schema/application/inplace_application/someof.rb +29 -0
- data/lib/jsi/schema/application/inplace_application.rb +46 -0
- data/lib/jsi/schema/application.rb +12 -0
- data/lib/jsi/schema/draft04.rb +14 -0
- data/lib/jsi/schema/draft06.rb +14 -0
- data/lib/jsi/schema/draft07.rb +14 -0
- data/lib/jsi/schema/issue.rb +36 -0
- data/lib/jsi/schema/ref.rb +159 -0
- data/lib/jsi/schema/schema_ancestor_node.rb +119 -0
- data/lib/jsi/schema/validation/array.rb +69 -0
- data/lib/jsi/schema/validation/const.rb +20 -0
- data/lib/jsi/schema/validation/contains.rb +25 -0
- data/lib/jsi/schema/validation/core.rb +39 -0
- data/lib/jsi/schema/validation/dependencies.rb +49 -0
- data/lib/jsi/schema/validation/draft04/minmax.rb +91 -0
- data/lib/jsi/schema/validation/draft04.rb +112 -0
- data/lib/jsi/schema/validation/draft06.rb +122 -0
- data/lib/jsi/schema/validation/draft07.rb +159 -0
- data/lib/jsi/schema/validation/enum.rb +25 -0
- data/lib/jsi/schema/validation/ifthenelse.rb +46 -0
- data/lib/jsi/schema/validation/items.rb +54 -0
- data/lib/jsi/schema/validation/not.rb +20 -0
- data/lib/jsi/schema/validation/numeric.rb +121 -0
- data/lib/jsi/schema/validation/object.rb +45 -0
- data/lib/jsi/schema/validation/pattern.rb +34 -0
- data/lib/jsi/schema/validation/properties.rb +101 -0
- data/lib/jsi/schema/validation/property_names.rb +32 -0
- data/lib/jsi/schema/validation/ref.rb +40 -0
- data/lib/jsi/schema/validation/required.rb +27 -0
- data/lib/jsi/schema/validation/someof.rb +90 -0
- data/lib/jsi/schema/validation/string.rb +47 -0
- data/lib/jsi/schema/validation/type.rb +49 -0
- data/lib/jsi/schema/validation.rb +51 -0
- data/lib/jsi/schema.rb +528 -233
- data/lib/jsi/schema_classes.rb +238 -51
- data/lib/jsi/schema_registry.rb +141 -0
- data/lib/jsi/schema_set.rb +141 -0
- data/lib/jsi/simple_wrap.rb +8 -3
- data/lib/jsi/typelike_modules.rb +75 -68
- data/lib/jsi/util/attr_struct.rb +106 -0
- data/lib/jsi/util.rb +167 -64
- data/lib/jsi/validation/error.rb +34 -0
- data/lib/jsi/validation/result.rb +210 -0
- data/lib/jsi/validation.rb +15 -0
- data/lib/jsi/version.rb +3 -1
- data/lib/jsi.rb +72 -9
- data/lib/schemas/json-schema.org/draft-04/schema.rb +12 -0
- data/lib/schemas/json-schema.org/draft-06/schema.rb +12 -0
- data/lib/schemas/json-schema.org/draft-07/schema.rb +12 -0
- data/readme.rb +138 -0
- data/{resources}/schemas/json-schema.org/draft-04/schema.json +149 -0
- data/{resources}/schemas/json-schema.org/draft-06/schema.json +154 -0
- data/{resources}/schemas/json-schema.org/draft-07/schema.json +168 -0
- metadata +80 -107
- data/.simplecov +0 -1
- data/LICENSE.txt +0 -21
- data/Rakefile.rb +0 -9
- data/jsi.gemspec +0 -31
- data/lib/jsi/base/to_rb.rb +0 -126
- data/lib/jsi/json/node.rb +0 -243
- data/lib/jsi/json/pointer.rb +0 -330
- data/lib/jsi/json-schema-fragments.rb +0 -59
- data/lib/jsi/json.rb +0 -8
- data/test/base_array_test.rb +0 -209
- data/test/base_hash_test.rb +0 -204
- data/test/base_test.rb +0 -422
- data/test/jsi_coder_test.rb +0 -85
- data/test/jsi_json_arraynode_test.rb +0 -150
- data/test/jsi_json_hashnode_test.rb +0 -132
- data/test/jsi_json_node_test.rb +0 -310
- data/test/jsi_json_pointer_test.rb +0 -106
- data/test/jsi_test.rb +0 -11
- data/test/jsi_typelike_as_json_test.rb +0 -53
- data/test/schema_test.rb +0 -196
- data/test/spreedly_openapi_test.rb +0 -8
- data/test/test_helper.rb +0 -63
- data/test/util_test.rb +0 -62
data/lib/jsi/base.rb
CHANGED
|
@@ -1,180 +1,324 @@
|
|
|
1
|
-
|
|
2
|
-
require 'jsi/typelike_modules'
|
|
1
|
+
# frozen_string_literal: true
|
|
3
2
|
|
|
4
3
|
module JSI
|
|
5
|
-
# the base class
|
|
4
|
+
# JSI::Base is the base class of every JSI instance of a JSON schema.
|
|
6
5
|
#
|
|
7
|
-
# a
|
|
8
|
-
#
|
|
6
|
+
# instances are described by a set of one or more JSON schemas. JSI dynamically creates a subclass of
|
|
7
|
+
# JSI::Base for each set of JSON schemas which describe an instance that is to be instantiated.
|
|
9
8
|
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
9
|
+
# a JSI instance of such a subclass represents a JSON schema instance described by that set of schemas.
|
|
10
|
+
#
|
|
11
|
+
# this subclass includes the JSI Schema Module of each schema it represents.
|
|
12
|
+
#
|
|
13
|
+
# the method {Base#jsi_schemas} is defined to indicate the schemas the class represents.
|
|
14
|
+
#
|
|
15
|
+
# the JSI::Base class itself is not intended to be instantiated.
|
|
13
16
|
class Base
|
|
14
|
-
include Memoize
|
|
15
|
-
include Enumerable
|
|
16
17
|
include PathedNode
|
|
18
|
+
include Schema::SchemaAncestorNode
|
|
19
|
+
include Util::Memoize
|
|
20
|
+
|
|
21
|
+
# not every JSI::Base is necessarily an Enumerable, but it's better to include Enumerable on
|
|
22
|
+
# the class than to conditionally extend the instance.
|
|
23
|
+
include Enumerable
|
|
24
|
+
|
|
25
|
+
# an exception raised when #[] is invoked on an instance which is not an array or hash
|
|
26
|
+
class CannotSubscriptError < StandardError
|
|
27
|
+
end
|
|
17
28
|
|
|
18
29
|
class << self
|
|
19
|
-
#
|
|
20
|
-
|
|
21
|
-
|
|
30
|
+
# @private @deprecated
|
|
31
|
+
def new_jsi(instance, **kw, &b)
|
|
32
|
+
new(instance, **kw, &b)
|
|
33
|
+
end
|
|
22
34
|
|
|
23
|
-
# @
|
|
24
|
-
#
|
|
25
|
-
|
|
26
|
-
|
|
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
|
|
27
42
|
end
|
|
28
43
|
|
|
29
|
-
#
|
|
44
|
+
# a string indicating a class name if one is defined, as well as the schema module name
|
|
45
|
+
# and/or schema URI of each schema the class represents.
|
|
46
|
+
# @return [String]
|
|
30
47
|
def inspect
|
|
31
|
-
|
|
32
|
-
if !respond_to?(:schema)
|
|
48
|
+
if !respond_to?(:jsi_class_schemas)
|
|
33
49
|
super
|
|
34
|
-
elsif in_schema_classes
|
|
35
|
-
%Q(#{SchemaClasses.inspect}[#{schema_id.inspect}])
|
|
36
|
-
elsif !name
|
|
37
|
-
%Q(#<Class for Schema: #{schema_id}>)
|
|
38
50
|
else
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
51
|
+
schema_names = jsi_class_schemas.map do |schema|
|
|
52
|
+
mod = schema.jsi_schema_module
|
|
53
|
+
if mod.name && schema.schema_uri
|
|
54
|
+
"#{mod.name} (#{schema.schema_uri})"
|
|
55
|
+
elsif mod.name
|
|
56
|
+
mod.name
|
|
57
|
+
elsif schema.schema_uri
|
|
58
|
+
schema.schema_uri.to_s
|
|
59
|
+
else
|
|
60
|
+
schema.jsi_ptr.uri.to_s
|
|
61
|
+
end
|
|
62
|
+
end
|
|
42
63
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
64
|
+
if name && !in_schema_classes
|
|
65
|
+
if jsi_class_schemas.empty?
|
|
66
|
+
"#{name} (0 schemas)"
|
|
67
|
+
else
|
|
68
|
+
"#{name} (#{schema_names.join(', ')})"
|
|
69
|
+
end
|
|
70
|
+
else
|
|
71
|
+
if schema_names.empty?
|
|
72
|
+
"(JSI Schema Class for 0 schemas)"
|
|
73
|
+
else
|
|
74
|
+
"(JSI Schema Class: #{schema_names.join(', ')})"
|
|
75
|
+
end
|
|
76
|
+
end
|
|
52
77
|
end
|
|
53
78
|
end
|
|
54
79
|
|
|
55
|
-
|
|
56
|
-
|
|
80
|
+
alias_method :to_s, :inspect
|
|
81
|
+
|
|
82
|
+
# @private
|
|
83
|
+
# see {.name}
|
|
57
84
|
def schema_classes_const_name
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
62
99
|
end
|
|
63
100
|
|
|
64
|
-
#
|
|
101
|
+
# a constant name of this class. this is generated from the schema module name or URI of each schema
|
|
102
|
+
# this class represents. nil if any represented schema has no schema module name or schema URI.
|
|
103
|
+
#
|
|
104
|
+
# this generated name is not too pretty but can be more helpful than an anonymous class, especially
|
|
105
|
+
# in error messages.
|
|
106
|
+
#
|
|
107
|
+
# @return [String]
|
|
65
108
|
def name
|
|
66
|
-
unless
|
|
67
|
-
|
|
68
|
-
|
|
109
|
+
unless instance_variable_defined?(:@in_schema_classes)
|
|
110
|
+
const_name = schema_classes_const_name
|
|
111
|
+
if super || !const_name || SchemaClasses.const_defined?(const_name)
|
|
112
|
+
@in_schema_classes = false
|
|
113
|
+
else
|
|
114
|
+
SchemaClasses.const_set(const_name, self)
|
|
115
|
+
@in_schema_classes = true
|
|
116
|
+
end
|
|
69
117
|
end
|
|
70
118
|
super
|
|
71
119
|
end
|
|
72
120
|
end
|
|
73
121
|
|
|
74
122
|
# NOINSTANCE is a magic value passed to #initialize when instantiating a JSI
|
|
75
|
-
# from a document and
|
|
76
|
-
|
|
123
|
+
# from a document and pointer.
|
|
124
|
+
#
|
|
125
|
+
# @private
|
|
126
|
+
NOINSTANCE = Object.new
|
|
127
|
+
[:inspect, :to_s].each(&(-> (s, m) { NOINSTANCE.define_singleton_method(m) { s } }.curry.("#{JSI::Base}::NOINSTANCE")))
|
|
128
|
+
NOINSTANCE.freeze
|
|
77
129
|
|
|
78
130
|
# initializes this JSI from the given instance - instance is most commonly
|
|
79
131
|
# a parsed JSON document consisting of Hash, Array, or sometimes a basic
|
|
80
132
|
# type, but this is in no way enforced and a JSI may wrap any object.
|
|
81
133
|
#
|
|
82
|
-
# @param instance [Object] the JSON Schema instance
|
|
134
|
+
# @param instance [Object] the JSON Schema instance to be represented as a JSI
|
|
83
135
|
# @param jsi_document [Object] for internal use. the instance may be specified as a
|
|
84
136
|
# node in the `jsi_document` param, pointed to by `jsi_ptr`. the param `instance`
|
|
85
137
|
# MUST be `NOINSTANCE` to use the jsi_document + jsi_ptr form. `jsi_document` MUST
|
|
86
138
|
# NOT be passed if `instance` is anything other than `NOINSTANCE`.
|
|
87
|
-
# @param jsi_ptr [JSI::
|
|
139
|
+
# @param jsi_ptr [JSI::Ptr] for internal use. a pointer specifying
|
|
88
140
|
# the path of this instance in the `jsi_document` param. `jsi_ptr` must be passed
|
|
89
141
|
# iff `jsi_document` is passed, i.e. when `instance` is `NOINSTANCE`
|
|
90
|
-
# @param
|
|
91
|
-
#
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
142
|
+
# @param jsi_root_node [JSI::Base] for internal use, specifies the JSI at the root of the document
|
|
143
|
+
# @param jsi_schema_base_uri [Addressable::URI] see {SchemaSet#new_jsi} param uri
|
|
144
|
+
# @param jsi_schema_resource_ancestors [Array<JSI::Base>]
|
|
145
|
+
def initialize(instance,
|
|
146
|
+
jsi_document: nil,
|
|
147
|
+
jsi_ptr: nil,
|
|
148
|
+
jsi_root_node: nil,
|
|
149
|
+
jsi_schema_base_uri: nil,
|
|
150
|
+
jsi_schema_resource_ancestors: []
|
|
151
|
+
)
|
|
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.")
|
|
95
154
|
end
|
|
96
155
|
|
|
97
|
-
if instance.is_a?(JSI::
|
|
98
|
-
raise(TypeError, "assigning
|
|
99
|
-
elsif instance.is_a?(JSI::
|
|
100
|
-
raise(TypeError, "assigning
|
|
156
|
+
if instance.is_a?(JSI::Schema)
|
|
157
|
+
raise(TypeError, "assigning a schema to a #{self.class.inspect} instance is incorrect. received: #{instance.pretty_inspect.chomp}")
|
|
158
|
+
elsif instance.is_a?(JSI::Base)
|
|
159
|
+
raise(TypeError, "assigning another JSI::Base instance to a #{self.class.inspect} instance is incorrect. received: #{instance.pretty_inspect.chomp}")
|
|
101
160
|
end
|
|
102
161
|
|
|
162
|
+
jsi_initialize_memos
|
|
163
|
+
|
|
103
164
|
if instance == NOINSTANCE
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
else
|
|
110
|
-
raise(Bug, 'incorrect usage') if jsi_document || jsi_ptr || ancestor_jsi
|
|
111
|
-
if instance.is_a?(PathedNode)
|
|
112
|
-
@jsi_document = instance.document_root_node
|
|
113
|
-
# this can result in the unusual situation where ancestor_jsi is nil, though jsi_ptr is not root.
|
|
114
|
-
# #document_root_node will then return a JSI::JSON::Pointer instead of a root JSI.
|
|
115
|
-
@jsi_ptr = instance.node_ptr
|
|
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
|
|
116
170
|
else
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
end
|
|
125
|
-
if !ancestor_jsi.jsi_ptr.contains?(@jsi_ptr)
|
|
126
|
-
raise(Bug, "ancestor_jsi ptr #{ancestor_jsi.jsi_ptr.inspect} is not ancestor of #{@jsi_ptr.inspect}")
|
|
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
|
|
127
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[]
|
|
183
|
+
@jsi_root_node = self
|
|
128
184
|
end
|
|
129
|
-
|
|
185
|
+
|
|
186
|
+
self.jsi_schema_base_uri = jsi_schema_base_uri
|
|
187
|
+
self.jsi_schema_resource_ancestors = jsi_schema_resource_ancestors
|
|
130
188
|
|
|
131
189
|
if self.jsi_instance.respond_to?(:to_hash)
|
|
132
|
-
extend
|
|
133
|
-
|
|
134
|
-
|
|
190
|
+
extend PathedHashNode
|
|
191
|
+
end
|
|
192
|
+
if self.jsi_instance.respond_to?(:to_ary)
|
|
193
|
+
extend PathedArrayNode
|
|
135
194
|
end
|
|
136
195
|
end
|
|
137
196
|
|
|
138
|
-
#
|
|
197
|
+
# @!method jsi_schemas
|
|
198
|
+
# the set of schemas which describe this instance
|
|
199
|
+
# @return [JSI::SchemaSet]
|
|
200
|
+
# note: defined on subclasses by JSI::SchemaClasses.class_for_schemas
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
# document containing the instance of this JSI at our {#jsi_ptr}
|
|
139
204
|
attr_reader :jsi_document
|
|
140
205
|
|
|
141
|
-
# JSI::
|
|
206
|
+
# {JSI::Ptr} pointing to this JSI's instance within our {#jsi_document}
|
|
207
|
+
# @return [JSI::Ptr]
|
|
142
208
|
attr_reader :jsi_ptr
|
|
143
209
|
|
|
144
|
-
#
|
|
145
|
-
|
|
210
|
+
# the JSI at the root of this JSI's document
|
|
211
|
+
# @return [JSI::Base]
|
|
212
|
+
attr_reader :jsi_root_node
|
|
146
213
|
|
|
147
|
-
|
|
148
|
-
alias_method :
|
|
214
|
+
# the JSON schema instance this JSI represents - the underlying JSON data used to instantiate this JSI
|
|
215
|
+
alias_method :jsi_instance, :jsi_node_content
|
|
149
216
|
|
|
150
|
-
#
|
|
151
|
-
|
|
152
|
-
|
|
217
|
+
# each is overridden by PathedHashNode or PathedArrayNode when appropriate. the base #each
|
|
218
|
+
# is not actually implemented, along with all the methods of Enumerable.
|
|
219
|
+
def each(*_)
|
|
220
|
+
raise NoMethodError, "Enumerable methods and #each not implemented for instance that is not like a hash or array: #{jsi_instance.pretty_inspect.chomp}"
|
|
221
|
+
end
|
|
153
222
|
|
|
154
|
-
#
|
|
155
|
-
#
|
|
156
|
-
|
|
157
|
-
|
|
223
|
+
# yields a JSI of each node at or below this one in this JSI's document.
|
|
224
|
+
#
|
|
225
|
+
# returns an Enumerator if no block is given.
|
|
226
|
+
#
|
|
227
|
+
# @yield [JSI::Base] each node in the document, starting with self
|
|
228
|
+
# @return [nil, Enumerator] an Enumerator if invoked without a block; otherwise nil
|
|
229
|
+
def jsi_each_child_node(&block)
|
|
230
|
+
return to_enum(__method__) unless block
|
|
231
|
+
|
|
232
|
+
yield self
|
|
233
|
+
if respond_to?(:to_hash)
|
|
234
|
+
each_key do |k|
|
|
235
|
+
self[k, as_jsi: true].jsi_each_child_node(&block)
|
|
236
|
+
end
|
|
237
|
+
elsif respond_to?(:to_ary)
|
|
238
|
+
each_index do |i|
|
|
239
|
+
self[i, as_jsi: true].jsi_each_child_node(&block)
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
nil
|
|
158
243
|
end
|
|
159
244
|
|
|
160
|
-
#
|
|
161
|
-
#
|
|
245
|
+
# recursively selects child nodes of this JSI, returning a modified copy of self containing only
|
|
246
|
+
# child nodes for which the given block had a true-ish result.
|
|
162
247
|
#
|
|
163
|
-
#
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
248
|
+
# 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 children are never recursed.
|
|
250
|
+
#
|
|
251
|
+
# @yield [JSI::Base] each child node below self
|
|
252
|
+
# @return [JSI::Base] modified copy of self containing only the selected nodes
|
|
253
|
+
def jsi_select_children_node_first(&block)
|
|
254
|
+
jsi_modified_copy do |instance|
|
|
255
|
+
if respond_to?(:to_hash)
|
|
256
|
+
res = instance.class.new
|
|
257
|
+
each_key do |k|
|
|
258
|
+
v = self[k, as_jsi: true]
|
|
259
|
+
if yield(v)
|
|
260
|
+
res[k] = v.jsi_select_children_node_first(&block).jsi_node_content
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
res
|
|
264
|
+
elsif respond_to?(:to_ary)
|
|
265
|
+
res = instance.class.new
|
|
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
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
res
|
|
273
|
+
else
|
|
274
|
+
instance
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# recursively selects child nodes of this JSI, returning a modified copy of self containing only
|
|
280
|
+
# child nodes for which the given block had a true-ish result.
|
|
281
|
+
#
|
|
282
|
+
# this method recursively descends child nodes before yielding each node, so leaf nodes are yielded
|
|
283
|
+
# before their parents.
|
|
284
|
+
#
|
|
285
|
+
# @yield [JSI::Base] each child node below self
|
|
286
|
+
# @return [JSI::Base] modified copy of self containing only the selected nodes
|
|
287
|
+
def jsi_select_children_leaf_first(&block)
|
|
288
|
+
jsi_modified_copy do |instance|
|
|
289
|
+
if respond_to?(:to_hash)
|
|
290
|
+
res = instance.class.new
|
|
291
|
+
each_key do |k|
|
|
292
|
+
v = self[k, as_jsi: true].jsi_select_children_leaf_first(&block)
|
|
293
|
+
if yield(v)
|
|
294
|
+
res[k] = v.jsi_node_content
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
res
|
|
298
|
+
elsif respond_to?(:to_ary)
|
|
299
|
+
res = instance.class.new
|
|
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
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
res
|
|
173
307
|
else
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
308
|
+
instance
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# an array of JSI instances above this one in the document.
|
|
314
|
+
#
|
|
315
|
+
# @return [Array<JSI::Base>]
|
|
316
|
+
def jsi_parent_nodes
|
|
317
|
+
parent = jsi_root_node
|
|
318
|
+
|
|
319
|
+
jsi_ptr.tokens.map do |token|
|
|
320
|
+
parent.tap do
|
|
321
|
+
parent = parent[token, as_jsi: true]
|
|
178
322
|
end
|
|
179
323
|
end.reverse
|
|
180
324
|
end
|
|
@@ -182,291 +326,294 @@ module JSI
|
|
|
182
326
|
# the immediate parent of this JSI. nil if there is no parent.
|
|
183
327
|
#
|
|
184
328
|
# @return [JSI::Base, nil]
|
|
185
|
-
def
|
|
186
|
-
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
# @return [JSI::PathedNode] a pathed node at the root of the document. this is generally a JSI::Base
|
|
190
|
-
# but may be a JSI::JSON::Node in unusual circumstances.
|
|
191
|
-
def document_root_node
|
|
192
|
-
if @jsi_ptr.root?
|
|
193
|
-
self
|
|
194
|
-
elsif @ancestor_jsi
|
|
195
|
-
@ancestor_jsi.document_root_node
|
|
196
|
-
elsif instance.is_a?(PathedNode)
|
|
197
|
-
instance.document_root_node
|
|
198
|
-
else
|
|
199
|
-
JSI::JSON::Node.new_doc(@jsi_document)
|
|
200
|
-
end
|
|
329
|
+
def jsi_parent_node
|
|
330
|
+
jsi_parent_nodes.first
|
|
201
331
|
end
|
|
202
332
|
|
|
203
|
-
#
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
333
|
+
# subscripts to return a child value identified by the given token.
|
|
334
|
+
#
|
|
335
|
+
# @param token [String, Integer, Object] an array index or hash key (JSON object property name)
|
|
336
|
+
# of the instance identifying the child value
|
|
337
|
+
# @param as_jsi [:auto, true, false] whether to return the result value as a JSI. one of:
|
|
338
|
+
#
|
|
339
|
+
# - :auto (default): by default a JSI will be returned when either:
|
|
340
|
+
#
|
|
341
|
+
# - the result is a complex value (responds to #to_ary or #to_hash) and is described by some schemas
|
|
342
|
+
# - the result is a schema (including true/false schemas)
|
|
343
|
+
#
|
|
344
|
+
# a plain value is returned when no schemas are known to describe the instance, or when the value is a
|
|
345
|
+
# simple type (anything unresponsive to #to_ary / #to_hash).
|
|
346
|
+
#
|
|
347
|
+
# - true: the result value will always be returned as a JSI. the #jsi_schemas of the result may be empty
|
|
348
|
+
# if no schemas describe the instance.
|
|
349
|
+
# - false: the result value will always be the plain instance.
|
|
350
|
+
#
|
|
351
|
+
# note that nil is returned (regardless of as_jsi) when there is no value to return because the token
|
|
352
|
+
# is not a hash key or array index of the instance and no default value applies.
|
|
353
|
+
# (one exception is when this JSI's instance is a Hash with a default or default_proc, which has
|
|
354
|
+
# unspecified behavior.)
|
|
355
|
+
# @param use_default [true, false] whether to return a schema default value when the token is not in
|
|
356
|
+
# range. if the token is not an array index or hash key of the instance, and one schema for the child
|
|
357
|
+
# instance specifies a default value, that default is returned.
|
|
358
|
+
#
|
|
359
|
+
# if the result with the default value is a JSI (per the `as_jsi` param), that JSI is not a child of
|
|
360
|
+
# this JSI - this JSI is not modified to fill in the default value. the result is a JSI within a new
|
|
361
|
+
# document containing the filled-in default.
|
|
362
|
+
#
|
|
363
|
+
# if the child instance's schemas do not indicate a single default value (that is, if zero or multiple
|
|
364
|
+
# defaults are specified across those schemas), nil is returned.
|
|
365
|
+
# (one exception is when this JSI's instance is a Hash with a default or default_proc, which has
|
|
366
|
+
# unspecified behavior.)
|
|
367
|
+
# @return [JSI::Base, Object] the child value identified by the subscript token
|
|
368
|
+
def [](token, as_jsi: :auto, use_default: true)
|
|
369
|
+
if respond_to?(:to_hash)
|
|
370
|
+
token_in_range = jsi_node_content_hash_pubsend(:key?, token)
|
|
371
|
+
value = jsi_node_content_hash_pubsend(:[], token)
|
|
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)
|
|
214
375
|
else
|
|
215
|
-
|
|
376
|
+
raise(CannotSubscriptError, "cannot subscript (using token: #{token.inspect}) from instance: #{jsi_instance.pretty_inspect.chomp}")
|
|
216
377
|
end
|
|
217
|
-
end
|
|
218
378
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
# @deprecated
|
|
222
|
-
alias_method :parent, :parent_jsi
|
|
379
|
+
begin
|
|
380
|
+
subinstance_schemas = jsi_subinstance_schemas_memos[token: token, instance: jsi_node_content, subinstance: value]
|
|
223
381
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
# JSI is not a $ref object, the block is not called. if we are a $ref which cannot be followed
|
|
229
|
-
# (e.g. a $ref to an external document, which is not yet supported), the block is not called.
|
|
230
|
-
# @return [JSI::Base, self]
|
|
231
|
-
def deref(&block)
|
|
232
|
-
node_ptr_deref do |deref_ptr|
|
|
233
|
-
jsi_from_root = deref_ptr.evaluate(document_root_node)
|
|
234
|
-
if jsi_from_root.is_a?(JSI::Base)
|
|
235
|
-
return jsi_from_root.tap(&(block || Util::NOOP))
|
|
382
|
+
if token_in_range
|
|
383
|
+
jsi_subinstance_as_jsi(value, subinstance_schemas, as_jsi) do
|
|
384
|
+
jsi_subinstance_memos[token: token, subinstance_schemas: subinstance_schemas]
|
|
385
|
+
end
|
|
236
386
|
else
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
|
395
|
+
|
|
396
|
+
if use_default && defaults.size == 1
|
|
397
|
+
# use the default value
|
|
398
|
+
# we are using #dup so that we get a modified copy of self, in which we set dup[token]=default.
|
|
399
|
+
dup.tap { |o| o[token] = defaults.first }[token, as_jsi: as_jsi]
|
|
241
400
|
else
|
|
242
|
-
|
|
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
|
|
243
406
|
end
|
|
244
|
-
return derefed.tap(&(block || Util::NOOP))
|
|
245
407
|
end
|
|
246
408
|
end
|
|
247
|
-
return self
|
|
248
409
|
end
|
|
249
410
|
|
|
250
|
-
#
|
|
251
|
-
# a
|
|
252
|
-
#
|
|
253
|
-
# the
|
|
254
|
-
# @
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
modified_ancestor = @ancestor_jsi.modified_copy do |anc|
|
|
262
|
-
mod_anc = @jsi_ptr.ptr_relative_to(@ancestor_jsi.jsi_ptr).modified_document_copy(anc, &block)
|
|
263
|
-
mod_anc
|
|
264
|
-
end
|
|
265
|
-
self.class.new(Base::NOINSTANCE, jsi_document: modified_ancestor.jsi_document, jsi_ptr: @jsi_ptr, ancestor_jsi: modified_ancestor)
|
|
411
|
+
# assigns the subscript of the instance identified by the given token to the given value.
|
|
412
|
+
# if the value is a JSI, its instance is assigned instead of the JSI value itself.
|
|
413
|
+
#
|
|
414
|
+
# @param token [String, Integer, Object] token identifying the subscript to assign
|
|
415
|
+
# @param value [JSI::Base, Object] the value to be assigned
|
|
416
|
+
def []=(token, value)
|
|
417
|
+
unless respond_to?(:to_hash) || respond_to?(:to_ary)
|
|
418
|
+
raise(NoMethodError, "cannot assign subscript (using token: #{token.inspect}) to instance: #{jsi_instance.pretty_inspect.chomp}")
|
|
419
|
+
end
|
|
420
|
+
if value.is_a?(Base)
|
|
421
|
+
self[token] = value.jsi_instance
|
|
266
422
|
else
|
|
423
|
+
jsi_instance[token] = value
|
|
424
|
+
end
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
# the set of JSI schema modules corresponding to the schemas that describe this JSI
|
|
428
|
+
# @return [Set<Module>]
|
|
429
|
+
def jsi_schema_modules
|
|
430
|
+
jsi_schemas.map(&:jsi_schema_module).to_set.freeze
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
# yields the content of this JSI's instance. the block must result in
|
|
434
|
+
# a modified copy of the yielded instance (not destructively modifying it)
|
|
435
|
+
# which will be used to instantiate a new JSI with the modified content.
|
|
436
|
+
#
|
|
437
|
+
# the result may have different schemas which describe it than this JSI's schemas,
|
|
438
|
+
# if conditional applicator schemas apply differently to the modified instance.
|
|
439
|
+
#
|
|
440
|
+
# @yield [Object] this JSI's instance. the block should result
|
|
441
|
+
# in a nondestructively modified copy of this.
|
|
442
|
+
# @return [JSI::Base subclass] the modified copy of self
|
|
443
|
+
def jsi_modified_copy(&block)
|
|
444
|
+
if @jsi_ptr.root?
|
|
267
445
|
modified_document = @jsi_ptr.modified_document_copy(@jsi_document, &block)
|
|
268
|
-
self.class.new(Base::NOINSTANCE,
|
|
446
|
+
self.class.new(Base::NOINSTANCE,
|
|
447
|
+
jsi_document: modified_document,
|
|
448
|
+
jsi_ptr: @jsi_ptr,
|
|
449
|
+
jsi_schema_base_uri: @jsi_schema_base_uri,
|
|
450
|
+
jsi_schema_resource_ancestors: @jsi_schema_resource_ancestors, # this can only be empty but included for consistency
|
|
451
|
+
)
|
|
452
|
+
else
|
|
453
|
+
modified_jsi_root_node = @jsi_root_node.jsi_modified_copy do |root|
|
|
454
|
+
@jsi_ptr.modified_document_copy(root, &block)
|
|
455
|
+
end
|
|
456
|
+
@jsi_ptr.evaluate(modified_jsi_root_node, as_jsi: true)
|
|
269
457
|
end
|
|
270
458
|
end
|
|
271
459
|
|
|
272
|
-
#
|
|
273
|
-
|
|
274
|
-
|
|
460
|
+
# validates this JSI's instance against its schemas
|
|
461
|
+
#
|
|
462
|
+
# @return [JSI::Validation::FullResult]
|
|
463
|
+
def jsi_validate
|
|
464
|
+
jsi_schemas.instance_validate(self)
|
|
275
465
|
end
|
|
276
466
|
|
|
277
|
-
#
|
|
278
|
-
|
|
279
|
-
|
|
467
|
+
# whether this JSI's instance is valid against all of its schemas
|
|
468
|
+
# @return [Boolean]
|
|
469
|
+
def jsi_valid?
|
|
470
|
+
jsi_schemas.instance_valid?(self)
|
|
280
471
|
end
|
|
281
472
|
|
|
282
|
-
# @
|
|
473
|
+
# @private
|
|
474
|
+
def fully_validate(errors_as_objects: false)
|
|
475
|
+
raise(NotImplementedError, "Base#fully_validate removed: see new validation interface Base#jsi_validate")
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
# @private
|
|
283
479
|
def validate
|
|
284
|
-
|
|
480
|
+
raise(NotImplementedError, "Base#validate renamed: see Base#jsi_valid?")
|
|
285
481
|
end
|
|
286
482
|
|
|
287
|
-
# @
|
|
288
|
-
# indicate a valid instance.
|
|
289
|
-
# @raise [::JSON::Schema::ValidationError] raises if the instance has
|
|
290
|
-
# validation errors
|
|
483
|
+
# @private
|
|
291
484
|
def validate!
|
|
292
|
-
|
|
485
|
+
raise(NotImplementedError, "Base#validate! removed")
|
|
293
486
|
end
|
|
294
487
|
|
|
295
488
|
def dup
|
|
296
|
-
|
|
489
|
+
jsi_modified_copy(&:dup)
|
|
297
490
|
end
|
|
298
491
|
|
|
299
|
-
#
|
|
300
|
-
#
|
|
492
|
+
# a string representing this JSI, indicating any named schemas and inspecting its instance
|
|
493
|
+
# @return [String]
|
|
301
494
|
def inspect
|
|
302
|
-
"\#<#{
|
|
495
|
+
"\#<#{jsi_object_group_text.join(' ')} #{jsi_instance.inspect}>"
|
|
303
496
|
end
|
|
304
497
|
|
|
305
|
-
# pretty-prints a representation this JSI to the given printer
|
|
498
|
+
# pretty-prints a representation of this JSI to the given printer
|
|
306
499
|
# @return [void]
|
|
307
500
|
def pretty_print(q)
|
|
308
|
-
q.
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
}
|
|
501
|
+
q.text '#<'
|
|
502
|
+
q.text jsi_object_group_text.join(' ')
|
|
503
|
+
q.group_sub {
|
|
504
|
+
q.nest(2) {
|
|
505
|
+
q.breakable ' '
|
|
506
|
+
q.pp jsi_instance
|
|
315
507
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
end
|
|
320
|
-
|
|
321
|
-
# @return [String] the instance's object_group_text
|
|
322
|
-
def object_group_text
|
|
323
|
-
instance.respond_to?(:object_group_text) ? instance.object_group_text : instance.class.inspect
|
|
324
|
-
end
|
|
325
|
-
|
|
326
|
-
# @return [Object] a jsonifiable representation of the instance
|
|
327
|
-
def as_json(*opt)
|
|
328
|
-
Typelike.as_json(instance, *opt)
|
|
329
|
-
end
|
|
330
|
-
|
|
331
|
-
# @return [Object] an opaque fingerprint of this JSI for FingerprintHash. JSIs are equal
|
|
332
|
-
# if their instances are equal, and if the JSIs are of the same JSI class or subclass.
|
|
333
|
-
def fingerprint
|
|
334
|
-
{class: jsi_class, jsi_document: jsi_document, jsi_ptr: jsi_ptr}
|
|
508
|
+
}
|
|
509
|
+
q.breakable ''
|
|
510
|
+
q.text '>'
|
|
335
511
|
end
|
|
336
|
-
include FingerprintHash
|
|
337
512
|
|
|
338
|
-
private
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
# module extending a {JSI::Base} object when its instance is Hash-like (responds to #to_hash)
|
|
360
|
-
module BaseHash
|
|
361
|
-
include PathedHashNode
|
|
362
|
-
|
|
363
|
-
alias_method :jsi_instance_hash_pubsend, :node_content_hash_pubsend
|
|
364
|
-
|
|
365
|
-
# @param property_name [String, Object] the property name to subscript
|
|
366
|
-
# @return [JSI::Base, Object] the instance's subscript value at the given
|
|
367
|
-
# key property_name_. if there is a subschema defined for that property
|
|
368
|
-
# on this JSI's schema, returns the instance's subscript as a JSI
|
|
369
|
-
# instiation of that subschema.
|
|
370
|
-
def [](property_name)
|
|
371
|
-
instance_property_key_ = jsi_instance_hash_pubsend(:key?, property_name)
|
|
372
|
-
if !instance_property_key_
|
|
373
|
-
deref do |deref_jsi|
|
|
374
|
-
return deref_jsi[property_name]
|
|
513
|
+
# @private
|
|
514
|
+
# @return [Array<String>]
|
|
515
|
+
def jsi_object_group_text
|
|
516
|
+
class_name = self.class.name unless self.class.in_schema_classes
|
|
517
|
+
class_txt = begin
|
|
518
|
+
if class_name
|
|
519
|
+
# ignore ID
|
|
520
|
+
schema_module_names = jsi_schemas.map { |schema| schema.jsi_schema_module.name }.compact
|
|
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
|
|
375
533
|
end
|
|
376
534
|
end
|
|
377
|
-
instance_property_value_ = jsi_instance_sub(property_name)
|
|
378
|
-
memoize(:[], property_name, instance_property_value_, instance_property_key_) do |property_name_, instance_property_value, instance_property_key|
|
|
379
|
-
begin
|
|
380
|
-
property_schema = schema.subschema_for_property(property_name_)
|
|
381
|
-
property_schema = property_schema && property_schema.match_to_instance(instance_property_value)
|
|
382
535
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
# this avoids duplication of code with #modified_copy and below in #[] to handle pathing and such.
|
|
389
|
-
dup.tap { |o| o[property_name_] = default }[property_name_]
|
|
390
|
-
else
|
|
391
|
-
default
|
|
392
|
-
end
|
|
393
|
-
elsif property_schema && (instance_property_value.respond_to?(:to_hash) || instance_property_value.respond_to?(:to_ary))
|
|
394
|
-
class_for_schema(property_schema).new(Base::NOINSTANCE, jsi_document: @jsi_document, jsi_ptr: @jsi_ptr[property_name_], ancestor_jsi: @ancestor_jsi || self)
|
|
395
|
-
else
|
|
396
|
-
instance_property_value
|
|
397
|
-
end
|
|
536
|
+
if (is_a?(PathedArrayNode) || is_a?(PathedHashNode)) && ![Array, Hash].include?(jsi_node_content.class)
|
|
537
|
+
if jsi_node_content.respond_to?(:jsi_object_group_text)
|
|
538
|
+
content_txt = jsi_node_content.jsi_object_group_text
|
|
539
|
+
else
|
|
540
|
+
content_txt = [jsi_node_content.class.to_s]
|
|
398
541
|
end
|
|
542
|
+
else
|
|
543
|
+
content_txt = []
|
|
399
544
|
end
|
|
400
|
-
end
|
|
401
545
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
# property_name
|
|
408
|
-
def []=(property_name, value)
|
|
409
|
-
subscript_assign(property_name, value)
|
|
546
|
+
[
|
|
547
|
+
class_txt,
|
|
548
|
+
is_a?(Metaschema) ? "Metaschema" : is_a?(Schema) ? "Schema" : nil,
|
|
549
|
+
*content_txt,
|
|
550
|
+
].compact
|
|
410
551
|
end
|
|
411
552
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
# @param token [String, Object]
|
|
553
|
+
# a jsonifiable representation of the instance
|
|
415
554
|
# @return [Object]
|
|
416
|
-
def
|
|
417
|
-
|
|
555
|
+
def as_json(*opt)
|
|
556
|
+
Typelike.as_json(jsi_instance, *opt)
|
|
418
557
|
end
|
|
419
|
-
end
|
|
420
|
-
|
|
421
|
-
# module extending a {JSI::Base} object when its instance is Array-like (responds to #to_ary)
|
|
422
|
-
module BaseArray
|
|
423
|
-
include PathedArrayNode
|
|
424
558
|
|
|
425
|
-
|
|
559
|
+
# an opaque fingerprint of this JSI for {Util::FingerprintHash}.
|
|
560
|
+
def jsi_fingerprint
|
|
561
|
+
{
|
|
562
|
+
class: jsi_class,
|
|
563
|
+
jsi_document: jsi_document,
|
|
564
|
+
jsi_ptr: jsi_ptr,
|
|
565
|
+
# for instances in documents with schemas:
|
|
566
|
+
jsi_resource_ancestor_uri: jsi_resource_ancestor_uri,
|
|
567
|
+
# only defined for JSI::Schema instances:
|
|
568
|
+
jsi_schema_instance_modules: is_a?(Schema) ? jsi_schema_instance_modules : nil,
|
|
569
|
+
}
|
|
570
|
+
end
|
|
571
|
+
include Util::FingerprintHash
|
|
426
572
|
|
|
427
|
-
|
|
428
|
-
# @return [JSI::Base, Object] the instance's subscript value at the given index
|
|
429
|
-
# i. if there is a subschema defined for that index on this JSI's schema,
|
|
430
|
-
# returns the instance's subscript as a JSI instiation of that subschema.
|
|
431
|
-
def [](i)
|
|
432
|
-
memoize(:[], i, jsi_instance_sub(i), jsi_instance_ary_pubsend(:each_index).to_a.include?(i)) do |i_, instance_idx_value, i_in_range|
|
|
433
|
-
begin
|
|
434
|
-
index_schema = schema.subschema_for_index(i_)
|
|
435
|
-
index_schema = index_schema && index_schema.match_to_instance(instance_idx_value)
|
|
573
|
+
private
|
|
436
574
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
default
|
|
575
|
+
def jsi_subinstance_schemas_memos
|
|
576
|
+
jsi_memomap(:subinstance_schemas, key_by: -> (i) { i[:token] }) do |token: , instance: , subinstance: |
|
|
577
|
+
SchemaSet.build do |schemas|
|
|
578
|
+
jsi_schemas.each do |schema|
|
|
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
|
|
446
583
|
end
|
|
447
|
-
elsif index_schema && (instance_idx_value.respond_to?(:to_hash) || instance_idx_value.respond_to?(:to_ary))
|
|
448
|
-
class_for_schema(index_schema).new(Base::NOINSTANCE, jsi_document: @jsi_document, jsi_ptr: @jsi_ptr[i_], ancestor_jsi: @ancestor_jsi || self)
|
|
449
|
-
else
|
|
450
|
-
instance_idx_value
|
|
451
584
|
end
|
|
452
585
|
end
|
|
453
586
|
end
|
|
454
587
|
end
|
|
455
588
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
589
|
+
def jsi_subinstance_memos
|
|
590
|
+
jsi_memomap(:subinstance, key_by: -> (i) { i[:token] }) do |token: , subinstance_schemas: |
|
|
591
|
+
JSI::SchemaClasses.class_for_schemas(subinstance_schemas).new(Base::NOINSTANCE,
|
|
592
|
+
jsi_document: @jsi_document,
|
|
593
|
+
jsi_ptr: @jsi_ptr[token],
|
|
594
|
+
jsi_root_node: @jsi_root_node,
|
|
595
|
+
jsi_schema_base_uri: jsi_resource_ancestor_uri,
|
|
596
|
+
jsi_schema_resource_ancestors: is_a?(Schema) ? jsi_subschema_resource_ancestors : jsi_schema_resource_ancestors,
|
|
597
|
+
)
|
|
598
|
+
end
|
|
462
599
|
end
|
|
463
600
|
|
|
464
|
-
|
|
601
|
+
def jsi_subinstance_as_jsi(value, subinstance_schemas, as_jsi)
|
|
602
|
+
value_as_jsi = if [true, false].include?(as_jsi)
|
|
603
|
+
as_jsi
|
|
604
|
+
elsif as_jsi == :auto
|
|
605
|
+
complex_value = subinstance_schemas.any? && (value.respond_to?(:to_hash) || value.respond_to?(:to_ary))
|
|
606
|
+
schema_value = subinstance_schemas.any? { |subinstance_schema| subinstance_schema.describes_schema? }
|
|
607
|
+
complex_value || schema_value
|
|
608
|
+
else
|
|
609
|
+
raise(ArgumentError, "as_jsi must be one of: :auto, true, false")
|
|
610
|
+
end
|
|
465
611
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
612
|
+
if value_as_jsi
|
|
613
|
+
yield
|
|
614
|
+
else
|
|
615
|
+
value
|
|
616
|
+
end
|
|
470
617
|
end
|
|
471
618
|
end
|
|
472
619
|
end
|