jsi 0.4.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +1 -1
- data/CHANGELOG.md +33 -0
- data/LICENSE.md +1 -1
- data/README.md +114 -42
- data/jsi.gemspec +14 -12
- data/lib/jsi/base/node.rb +183 -0
- data/lib/jsi/base.rb +388 -220
- data/lib/jsi/jsi_coder.rb +8 -7
- data/lib/jsi/metaschema.rb +0 -1
- data/lib/jsi/metaschema_node/bootstrap_schema.rb +101 -0
- data/lib/jsi/metaschema_node.rb +159 -135
- data/lib/jsi/ptr.rb +303 -0
- data/lib/jsi/schema/application/child_application/contains.rb +25 -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 +38 -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 +44 -0
- data/lib/jsi/schema/application/inplace_application.rb +41 -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 +160 -0
- data/lib/jsi/schema/schema_ancestor_node.rb +113 -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 +508 -149
- data/lib/jsi/schema_classes.rb +199 -59
- data/lib/jsi/schema_registry.rb +151 -0
- data/lib/jsi/schema_set.rb +181 -0
- data/lib/jsi/simple_wrap.rb +23 -4
- data/lib/jsi/util/private/attr_struct.rb +127 -0
- data/lib/jsi/util/private.rb +204 -0
- data/lib/jsi/util/typelike.rb +229 -0
- data/lib/jsi/util.rb +89 -53
- 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 +44 -14
- data/lib/schemas/json-schema.org/draft-04/schema.rb +10 -3
- data/lib/schemas/json-schema.org/draft-06/schema.rb +10 -3
- data/lib/schemas/json-schema.org/draft-07/schema.rb +14 -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 +75 -122
- data/.simplecov +0 -3
- data/Rakefile.rb +0 -9
- data/lib/jsi/base/to_rb.rb +0 -128
- data/lib/jsi/json/node.rb +0 -203
- data/lib/jsi/json/pointer.rb +0 -419
- data/lib/jsi/json-schema-fragments.rb +0 -61
- data/lib/jsi/json.rb +0 -10
- data/lib/jsi/pathed_node.rb +0 -118
- data/lib/jsi/typelike_modules.rb +0 -240
- data/resources/icons/AGPL-3.0.png +0 -0
- data/test/base_array_test.rb +0 -323
- data/test/base_hash_test.rb +0 -337
- data/test/base_test.rb +0 -486
- 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 -257
- data/test/jsi_json_pointer_test.rb +0 -102
- data/test/jsi_test.rb +0 -11
- data/test/jsi_typelike_as_json_test.rb +0 -53
- data/test/metaschema_node_test.rb +0 -19
- data/test/schema_module_test.rb +0 -21
- data/test/schema_test.rb +0 -208
- data/test/spreedly_openapi_test.rb +0 -8
- data/test/test_helper.rb +0 -97
- data/test/util_test.rb +0 -62
data/lib/jsi/json/pointer.rb
DELETED
@@ -1,419 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module JSI
|
4
|
-
module JSON
|
5
|
-
# a JSON Pointer, as described by RFC 6901 https://tools.ietf.org/html/rfc6901
|
6
|
-
class Pointer
|
7
|
-
class Error < StandardError
|
8
|
-
end
|
9
|
-
class PointerSyntaxError < Error
|
10
|
-
end
|
11
|
-
class ReferenceError < Error
|
12
|
-
end
|
13
|
-
|
14
|
-
# instantiates a Pointer from any given reference tokens.
|
15
|
-
#
|
16
|
-
# >> JSI::JSON::Pointer[]
|
17
|
-
# => #<JSI::JSON::Pointer reference_tokens: []>
|
18
|
-
# >> JSI::JSON::Pointer['a', 'b']
|
19
|
-
# => #<JSI::JSON::Pointer reference_tokens: ["a", "b"]>
|
20
|
-
# >> JSI::JSON::Pointer['a']['b']
|
21
|
-
# => #<JSI::JSON::Pointer reference_tokens: ["a", "b"]>
|
22
|
-
#
|
23
|
-
# note in the last example that you can conveniently chain the class .[] method
|
24
|
-
# with the instance #[] method.
|
25
|
-
#
|
26
|
-
# @param *reference_tokens any number of reference tokens
|
27
|
-
# @return [JSI::JSON::Pointer]
|
28
|
-
def self.[](*reference_tokens)
|
29
|
-
new(reference_tokens)
|
30
|
-
end
|
31
|
-
|
32
|
-
# parse a URI-escaped fragment and instantiate as a JSI::JSON::Pointer
|
33
|
-
#
|
34
|
-
# ptr = JSI::JSON::Pointer.from_fragment('/foo/bar')
|
35
|
-
# => #<JSI::JSON::Pointer fragment: /foo/bar>
|
36
|
-
# ptr.reference_tokens
|
37
|
-
# => ["foo", "bar"]
|
38
|
-
#
|
39
|
-
# with URI escaping:
|
40
|
-
#
|
41
|
-
# ptr = JSI::JSON::Pointer.from_fragment('/foo%20bar')
|
42
|
-
# => #<JSI::JSON::Pointer fragment: /foo%20bar>
|
43
|
-
# ptr.reference_tokens
|
44
|
-
# => ["foo bar"]
|
45
|
-
#
|
46
|
-
# @param fragment [String] a fragment containing a pointer (starting with #)
|
47
|
-
# @return [JSI::JSON::Pointer]
|
48
|
-
# @raise [JSI::JSON::Pointer::PointerSyntaxError] when the fragment does not contain a pointer with valid pointer syntax
|
49
|
-
def self.from_fragment(fragment)
|
50
|
-
from_pointer(Addressable::URI.unescape(fragment), type: 'fragment')
|
51
|
-
end
|
52
|
-
|
53
|
-
# parse a pointer string and instantiate as a JSI::JSON::Pointer
|
54
|
-
#
|
55
|
-
# ptr1 = JSI::JSON::Pointer.from_pointer('/foo')
|
56
|
-
# => #<JSI::JSON::Pointer pointer: /foo>
|
57
|
-
# ptr1.reference_tokens
|
58
|
-
# => ["foo"]
|
59
|
-
#
|
60
|
-
# ptr2 = JSI::JSON::Pointer.from_pointer('/foo~0bar/baz~1qux')
|
61
|
-
# => #<JSI::JSON::Pointer pointer: /foo~0bar/baz~1qux>
|
62
|
-
# ptr2.reference_tokens
|
63
|
-
# => ["foo~bar", "baz/qux"]
|
64
|
-
#
|
65
|
-
# @param pointer_string [String] a pointer string
|
66
|
-
# @param type (for internal use) indicates the original representation of the pointer
|
67
|
-
# @return [JSI::JSON::Pointer]
|
68
|
-
# @raise [JSI::JSON::Pointer::PointerSyntaxError] when the pointer_string does not have valid pointer syntax
|
69
|
-
def self.from_pointer(pointer_string, type: 'pointer')
|
70
|
-
tokens = pointer_string.split('/', -1).map! do |piece|
|
71
|
-
piece.gsub('~1', '/').gsub('~0', '~')
|
72
|
-
end
|
73
|
-
if tokens[0] == ''
|
74
|
-
new(tokens[1..-1], type: type)
|
75
|
-
elsif tokens.empty?
|
76
|
-
new(tokens, type: type)
|
77
|
-
else
|
78
|
-
raise(PointerSyntaxError, "Invalid pointer syntax in #{pointer_string.inspect}: pointer must begin with /")
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
# initializes a JSI::JSON::Pointer from the given reference_tokens.
|
83
|
-
#
|
84
|
-
# @param reference_tokens [Array<Object>]
|
85
|
-
# @param type [String, Symbol] one of 'pointer' or 'fragment'
|
86
|
-
def initialize(reference_tokens, type: nil)
|
87
|
-
unless reference_tokens.respond_to?(:to_ary)
|
88
|
-
raise(TypeError, "reference_tokens must be an array. got: #{reference_tokens.inspect}")
|
89
|
-
end
|
90
|
-
@reference_tokens = reference_tokens.to_ary.map(&:freeze).freeze
|
91
|
-
@type = type.is_a?(Symbol) ? type.to_s : type
|
92
|
-
end
|
93
|
-
|
94
|
-
attr_reader :reference_tokens
|
95
|
-
|
96
|
-
# takes a root json document and evaluates this pointer through the document, returning the value
|
97
|
-
# pointed to by this pointer.
|
98
|
-
#
|
99
|
-
# @param document [#to_ary, #to_hash] the document against which we will evaluate this pointer
|
100
|
-
# @return [Object] the content of the document pointed to by this pointer
|
101
|
-
# @raise [JSI::JSON::Pointer::ReferenceError] the document does not contain the path this pointer references
|
102
|
-
def evaluate(document)
|
103
|
-
res = reference_tokens.inject(document) do |value, token|
|
104
|
-
if value.respond_to?(:to_ary)
|
105
|
-
if token.is_a?(String) && token =~ /\A\d|[1-9]\d+\z/
|
106
|
-
token = token.to_i
|
107
|
-
end
|
108
|
-
unless token.is_a?(Integer)
|
109
|
-
raise(ReferenceError, "Invalid resolution for #{to_s}: #{token.inspect} is not an integer and cannot be resolved in array #{value.inspect}")
|
110
|
-
end
|
111
|
-
unless (0...(value.respond_to?(:size) ? value : value.to_ary).size).include?(token)
|
112
|
-
raise(ReferenceError, "Invalid resolution for #{to_s}: #{token.inspect} is not a valid index of #{value.inspect}")
|
113
|
-
end
|
114
|
-
(value.respond_to?(:[]) ? value : value.to_ary)[token]
|
115
|
-
elsif value.respond_to?(:to_hash)
|
116
|
-
unless (value.respond_to?(:key?) ? value : value.to_hash).key?(token)
|
117
|
-
raise(ReferenceError, "Invalid resolution for #{to_s}: #{token.inspect} is not a valid key of #{value.inspect}")
|
118
|
-
end
|
119
|
-
(value.respond_to?(:[]) ? value : value.to_hash)[token]
|
120
|
-
else
|
121
|
-
raise(ReferenceError, "Invalid resolution for #{to_s}: #{token.inspect} cannot be resolved in #{value.inspect}")
|
122
|
-
end
|
123
|
-
end
|
124
|
-
res
|
125
|
-
end
|
126
|
-
|
127
|
-
# @return [String] the pointer string representation of this Pointer
|
128
|
-
def pointer
|
129
|
-
reference_tokens.map { |t| '/' + t.to_s.gsub('~', '~0').gsub('/', '~1') }.join('')
|
130
|
-
end
|
131
|
-
|
132
|
-
# @return [String] the fragment string representation of this Pointer
|
133
|
-
def fragment
|
134
|
-
Addressable::URI.escape(pointer)
|
135
|
-
end
|
136
|
-
|
137
|
-
# @return [Addressable::URI] a URI consisting only of a pointer fragment
|
138
|
-
def uri
|
139
|
-
Addressable::URI.new(fragment: fragment)
|
140
|
-
end
|
141
|
-
|
142
|
-
# @return [Boolean] whether this pointer points to the root (has an empty array of reference_tokens)
|
143
|
-
def root?
|
144
|
-
reference_tokens.empty?
|
145
|
-
end
|
146
|
-
|
147
|
-
# @return [JSI::JSON::Pointer] pointer to the parent of where this pointer points
|
148
|
-
# @raise [JSI::JSON::Pointer::ReferenceError] if this pointer has no parent (points to the root)
|
149
|
-
def parent
|
150
|
-
if root?
|
151
|
-
raise(ReferenceError, "cannot access parent of root pointer: #{pretty_inspect.chomp}")
|
152
|
-
else
|
153
|
-
Pointer.new(reference_tokens[0...-1], type: @type)
|
154
|
-
end
|
155
|
-
end
|
156
|
-
|
157
|
-
# @return [Boolean] does this pointer contain the other_ptr - that is, is this pointer an
|
158
|
-
# ancestor of other_ptr, a child pointer. contains? is inclusive; a pointer does contain itself.
|
159
|
-
def contains?(other_ptr)
|
160
|
-
self.reference_tokens == other_ptr.reference_tokens[0...self.reference_tokens.size]
|
161
|
-
end
|
162
|
-
|
163
|
-
# @return [JSI::JSON::Pointer] returns this pointer relative to the given ancestor_ptr
|
164
|
-
# @raise [JSI::JSON::Pointer::ReferenceError] if the given ancestor_ptr is not an ancestor of this pointer
|
165
|
-
def ptr_relative_to(ancestor_ptr)
|
166
|
-
unless ancestor_ptr.contains?(self)
|
167
|
-
raise(ReferenceError, "ancestor_ptr #{ancestor_ptr.inspect} is not ancestor of #{inspect}")
|
168
|
-
end
|
169
|
-
Pointer.new(reference_tokens[ancestor_ptr.reference_tokens.size..-1], type: @type)
|
170
|
-
end
|
171
|
-
|
172
|
-
# @param ptr [JSI::JSON::Pointer]
|
173
|
-
# @return [JSI::JSON::Pointer] a pointer with the reference tokens of this one plus the given ptr's.
|
174
|
-
def +(ptr)
|
175
|
-
unless ptr.is_a?(JSI::JSON::Pointer)
|
176
|
-
raise(TypeError, "ptr must be a JSI::JSON::Pointer; got: #{ptr.inspect}")
|
177
|
-
end
|
178
|
-
Pointer.new(reference_tokens + ptr.reference_tokens, type: @type)
|
179
|
-
end
|
180
|
-
|
181
|
-
# @param n [Integer]
|
182
|
-
# @return [JSI::JSON::Pointer] a Pointer consisting of the first n of our reference_tokens
|
183
|
-
# @raise [ArgumentError] if n is not between 0 and the size of our reference_tokens
|
184
|
-
def take(n)
|
185
|
-
unless (0..reference_tokens.size).include?(n)
|
186
|
-
raise(ArgumentError, "n not in range (0..#{reference_tokens.size}): #{n.inspect}")
|
187
|
-
end
|
188
|
-
Pointer.new(reference_tokens.take(n), type: @type)
|
189
|
-
end
|
190
|
-
|
191
|
-
# appends the given token to this Pointer's reference tokens and returns the result
|
192
|
-
#
|
193
|
-
# @param token [Object]
|
194
|
-
# @return [JSI::JSON::Pointer] pointer to a child node of this pointer with the given token
|
195
|
-
def [](token)
|
196
|
-
Pointer.new(reference_tokens + [token], type: @type)
|
197
|
-
end
|
198
|
-
|
199
|
-
# given this Pointer points to a schema in the given document, returns a set of pointers
|
200
|
-
# to subschemas of that schema for the given property name.
|
201
|
-
#
|
202
|
-
# @param document [#to_hash, #to_ary, Object] document containing the schema this pointer points to
|
203
|
-
# @param property_name [Object] the property name for which to find a subschema
|
204
|
-
# @return [Set<JSI::JSON::Pointer>] pointers to subschemas
|
205
|
-
def schema_subschema_ptrs_for_property_name(document, property_name)
|
206
|
-
ptr = self
|
207
|
-
schema = ptr.evaluate(document)
|
208
|
-
Set.new.tap do |ptrs|
|
209
|
-
if schema.respond_to?(:to_hash)
|
210
|
-
apply_additional = true
|
211
|
-
if schema.key?('properties') && schema['properties'].respond_to?(:to_hash) && schema['properties'].key?(property_name)
|
212
|
-
apply_additional = false
|
213
|
-
ptrs << ptr['properties'][property_name]
|
214
|
-
end
|
215
|
-
if schema['patternProperties'].respond_to?(:to_hash)
|
216
|
-
schema['patternProperties'].each_key do |pattern|
|
217
|
-
if property_name.to_s =~ Regexp.new(pattern) # TODO map pattern to ruby syntax
|
218
|
-
apply_additional = false
|
219
|
-
ptrs << ptr['patternProperties'][pattern]
|
220
|
-
end
|
221
|
-
end
|
222
|
-
end
|
223
|
-
if apply_additional && schema.key?('additionalProperties')
|
224
|
-
ptrs << ptr['additionalProperties']
|
225
|
-
end
|
226
|
-
end
|
227
|
-
end
|
228
|
-
end
|
229
|
-
|
230
|
-
# given this Pointer points to a schema in the given document, returns a set of pointers
|
231
|
-
# to subschemas of that schema for the given array index.
|
232
|
-
#
|
233
|
-
# @param document [#to_hash, #to_ary, Object] document containing the schema this pointer points to
|
234
|
-
# @param idx [Object] the array index for which to find subschemas
|
235
|
-
# @return [Set<JSI::JSON::Pointer>] pointers to subschemas
|
236
|
-
def schema_subschema_ptrs_for_index(document, idx)
|
237
|
-
ptr = self
|
238
|
-
schema = ptr.evaluate(document)
|
239
|
-
Set.new.tap do |ptrs|
|
240
|
-
if schema.respond_to?(:to_hash)
|
241
|
-
if schema['items'].respond_to?(:to_ary)
|
242
|
-
if schema['items'].each_index.to_a.include?(idx)
|
243
|
-
ptrs << ptr['items'][idx]
|
244
|
-
elsif schema.key?('additionalItems')
|
245
|
-
ptrs << ptr['additionalItems']
|
246
|
-
end
|
247
|
-
elsif schema.key?('items')
|
248
|
-
ptrs << ptr['items']
|
249
|
-
end
|
250
|
-
end
|
251
|
-
end
|
252
|
-
end
|
253
|
-
|
254
|
-
# given this Pointer points to a schema in the given document, this matches any
|
255
|
-
# applicators of the schema (oneOf, anyOf, allOf, $ref) which should be applied
|
256
|
-
# and returns them as a set of pointers.
|
257
|
-
#
|
258
|
-
# @param document [#to_hash, #to_ary, Object] document containing the schema this pointer points to
|
259
|
-
# @param instance [Object] the instance to check any applicators against
|
260
|
-
# @return [JSI::JSON::Pointer] either a pointer to a *Of subschema in the document,
|
261
|
-
# or self if no other subschema was matched
|
262
|
-
def schema_match_ptrs_to_instance(document, instance)
|
263
|
-
ptr = self
|
264
|
-
schema = ptr.evaluate(document)
|
265
|
-
|
266
|
-
Set.new.tap do |ptrs|
|
267
|
-
if schema.respond_to?(:to_hash)
|
268
|
-
if schema['$ref'].respond_to?(:to_str)
|
269
|
-
ptr.deref(document) do |deref_ptr|
|
270
|
-
ptrs.merge(deref_ptr.schema_match_ptrs_to_instance(document, instance))
|
271
|
-
end
|
272
|
-
else
|
273
|
-
ptrs << ptr
|
274
|
-
end
|
275
|
-
if schema['allOf'].respond_to?(:to_ary)
|
276
|
-
schema['allOf'].each_index do |i|
|
277
|
-
ptrs.merge(ptr['allOf'][i].schema_match_ptrs_to_instance(document, instance))
|
278
|
-
end
|
279
|
-
end
|
280
|
-
if schema['anyOf'].respond_to?(:to_ary)
|
281
|
-
schema['anyOf'].each_index do |i|
|
282
|
-
valid = ::JSON::Validator.validate(JSI::Typelike.as_json(document), JSI::Typelike.as_json(instance), fragment: ptr['anyOf'][i].fragment)
|
283
|
-
if valid
|
284
|
-
ptrs.merge(ptr['anyOf'][i].schema_match_ptrs_to_instance(document, instance))
|
285
|
-
end
|
286
|
-
end
|
287
|
-
end
|
288
|
-
if schema['oneOf'].respond_to?(:to_ary)
|
289
|
-
one_i = schema['oneOf'].each_index.detect do |i|
|
290
|
-
::JSON::Validator.validate(JSI::Typelike.as_json(document), JSI::Typelike.as_json(instance), fragment: ptr['oneOf'][i].fragment)
|
291
|
-
end
|
292
|
-
if one_i
|
293
|
-
ptrs.merge(ptr['oneOf'][one_i].schema_match_ptrs_to_instance(document, instance))
|
294
|
-
end
|
295
|
-
end
|
296
|
-
# TODO dependencies
|
297
|
-
else
|
298
|
-
ptrs << ptr
|
299
|
-
end
|
300
|
-
end
|
301
|
-
end
|
302
|
-
|
303
|
-
# takes a document and a block. the block is yielded the content of the given document at this
|
304
|
-
# pointer's location. the block must result a modified copy of that content (and MUST NOT modify
|
305
|
-
# the object it is given). this modified copy of that content is incorporated into a modified copy
|
306
|
-
# of the given document, which is then returned. the structure and contents of the document outside
|
307
|
-
# the path pointed to by this pointer is not modified.
|
308
|
-
#
|
309
|
-
# @param document [Object] the document to apply this pointer to
|
310
|
-
# @yield [Object] the content this pointer applies to in the given document
|
311
|
-
# the block must result in the new content which will be placed in the modified document copy.
|
312
|
-
# @return [Object] a copy of the given document, with the content this pointer applies to
|
313
|
-
# replaced by the result of the block
|
314
|
-
def modified_document_copy(document, &block)
|
315
|
-
# we need to preserve the rest of the document, but modify the content at our path.
|
316
|
-
#
|
317
|
-
# this is actually a bit tricky. we can't modify the original document, obviously.
|
318
|
-
# we could do a deep copy, but that's expensive. instead, we make a copy of each array
|
319
|
-
# or hash in the path above this node. this node's content is modified by the caller, and
|
320
|
-
# that is recursively merged up to the document root. the recursion is done with a
|
321
|
-
# y combinator, for no other reason than that was a fun way to implement it.
|
322
|
-
modified_document = JSI::Util.ycomb do |rec|
|
323
|
-
proc do |subdocument, subpath|
|
324
|
-
if subpath == []
|
325
|
-
Typelike.modified_copy(subdocument, &block)
|
326
|
-
else
|
327
|
-
car = subpath[0]
|
328
|
-
cdr = subpath[1..-1]
|
329
|
-
if subdocument.respond_to?(:to_hash)
|
330
|
-
subdocument_car = (subdocument.respond_to?(:[]) ? subdocument : subdocument.to_hash)[car]
|
331
|
-
car_object = rec.call(subdocument_car, cdr)
|
332
|
-
if car_object.object_id == subdocument_car.object_id
|
333
|
-
subdocument
|
334
|
-
else
|
335
|
-
(subdocument.respond_to?(:merge) ? subdocument : subdocument.to_hash).merge({car => car_object})
|
336
|
-
end
|
337
|
-
elsif subdocument.respond_to?(:to_ary)
|
338
|
-
if car.is_a?(String) && car =~ /\A\d+\z/
|
339
|
-
car = car.to_i
|
340
|
-
end
|
341
|
-
unless car.is_a?(Integer)
|
342
|
-
raise(TypeError, "bad subscript #{car.pretty_inspect.chomp} with remaining subpath: #{cdr.inspect} for array: #{subdocument.pretty_inspect.chomp}")
|
343
|
-
end
|
344
|
-
subdocument_car = (subdocument.respond_to?(:[]) ? subdocument : subdocument.to_ary)[car]
|
345
|
-
car_object = rec.call(subdocument_car, cdr)
|
346
|
-
if car_object.object_id == subdocument_car.object_id
|
347
|
-
subdocument
|
348
|
-
else
|
349
|
-
(subdocument.respond_to?(:[]=) ? subdocument : subdocument.to_ary).dup.tap do |arr|
|
350
|
-
arr[car] = car_object
|
351
|
-
end
|
352
|
-
end
|
353
|
-
else
|
354
|
-
raise(TypeError, "bad subscript: #{car.pretty_inspect.chomp} with remaining subpath: #{cdr.inspect} for content: #{subdocument.pretty_inspect.chomp}")
|
355
|
-
end
|
356
|
-
end
|
357
|
-
end
|
358
|
-
end.call(document, reference_tokens)
|
359
|
-
modified_document
|
360
|
-
end
|
361
|
-
|
362
|
-
# if this Pointer points at a $ref node within the given document, #deref attempts
|
363
|
-
# to follow that $ref and return a Pointer to the referenced location. otherwise,
|
364
|
-
# this Pointer is returned.
|
365
|
-
#
|
366
|
-
# if the content this Pointer points to in the document is not hash-like, does not
|
367
|
-
# have a $ref property, its $ref cannot be found, or its $ref points outside the document,
|
368
|
-
# this pointer is returned.
|
369
|
-
#
|
370
|
-
# @param document [Object] the document this pointer applies to
|
371
|
-
# @yield [Pointer] if a block is given (optional), this will yield a deref'd pointer. if this
|
372
|
-
# pointer does not point to a $ref object in the given document, the block is not called.
|
373
|
-
# if we point to a $ref which cannot be followed (e.g. a $ref to an external
|
374
|
-
# document, which is not yet supported), the block is not called.
|
375
|
-
# @return [Pointer] dereferenced pointer, or this pointer
|
376
|
-
def deref(document, &block)
|
377
|
-
block ||= Util::NOOP
|
378
|
-
content = evaluate(document)
|
379
|
-
|
380
|
-
if content.respond_to?(:to_hash)
|
381
|
-
ref = (content.respond_to?(:[]) ? content : content.to_hash)['$ref']
|
382
|
-
end
|
383
|
-
return self unless ref.is_a?(String)
|
384
|
-
|
385
|
-
if ref[/\A#/]
|
386
|
-
return Pointer.from_fragment(Addressable::URI.parse(ref).fragment).tap(&block)
|
387
|
-
end
|
388
|
-
|
389
|
-
# HAX for how google does refs and ids
|
390
|
-
if document['schemas'].respond_to?(:to_hash)
|
391
|
-
if document['schemas'][ref]
|
392
|
-
return Pointer.new(['schemas', ref], type: 'hax').tap(&block)
|
393
|
-
end
|
394
|
-
document['schemas'].each do |k, schema|
|
395
|
-
if schema['id'] == ref
|
396
|
-
return Pointer.new(['schemas', k], type: 'hax').tap(&block)
|
397
|
-
end
|
398
|
-
end
|
399
|
-
end
|
400
|
-
|
401
|
-
#raise(NotImplementedError, "cannot dereference #{ref}") # TODO
|
402
|
-
return self
|
403
|
-
end
|
404
|
-
|
405
|
-
# @return [String] string representation of this Pointer
|
406
|
-
def inspect
|
407
|
-
"#{self.class.name}[#{reference_tokens.map(&:inspect).join(", ")}]"
|
408
|
-
end
|
409
|
-
|
410
|
-
alias_method :to_s, :inspect
|
411
|
-
|
412
|
-
# pointers are equal if the reference_tokens are equal, regardless of @type
|
413
|
-
def jsi_fingerprint
|
414
|
-
{class: JSI::JSON::Pointer, reference_tokens: reference_tokens}
|
415
|
-
end
|
416
|
-
include Util::FingerprintHash
|
417
|
-
end
|
418
|
-
end
|
419
|
-
end
|
@@ -1,61 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "json-schema"
|
4
|
-
|
5
|
-
# apply the changes from https://github.com/ruby-json-schema/json-schema/pull/382
|
6
|
-
|
7
|
-
# json-schema/validator.rb
|
8
|
-
|
9
|
-
module JSON
|
10
|
-
class Validator
|
11
|
-
def initialize(schema_data, data, opts={})
|
12
|
-
@options = @@default_opts.clone.merge(opts)
|
13
|
-
@errors = []
|
14
|
-
|
15
|
-
validator = self.class.validator_for_name(@options[:version])
|
16
|
-
@options[:version] = validator
|
17
|
-
@options[:schema_reader] ||= self.class.schema_reader
|
18
|
-
|
19
|
-
@validation_options = @options[:record_errors] ? {:record_errors => true} : {}
|
20
|
-
@validation_options[:insert_defaults] = true if @options[:insert_defaults]
|
21
|
-
@validation_options[:strict] = true if @options[:strict] == true
|
22
|
-
@validation_options[:clear_cache] = true if !@@cache_schemas || @options[:clear_cache]
|
23
|
-
|
24
|
-
@@mutex.synchronize { @base_schema = initialize_schema(schema_data) }
|
25
|
-
@original_data = data
|
26
|
-
@data = initialize_data(data)
|
27
|
-
@@mutex.synchronize { build_schemas(@base_schema) }
|
28
|
-
|
29
|
-
# If the :fragment option is set, try and validate against the fragment
|
30
|
-
if opts[:fragment]
|
31
|
-
@base_schema = schema_from_fragment(@base_schema, opts[:fragment])
|
32
|
-
end
|
33
|
-
|
34
|
-
# validate the schema, if requested
|
35
|
-
if @options[:validate_schema]
|
36
|
-
if @base_schema.schema["$schema"]
|
37
|
-
base_validator = self.class.validator_for_name(@base_schema.schema["$schema"])
|
38
|
-
end
|
39
|
-
metaschema = base_validator ? base_validator.metaschema : validator.metaschema
|
40
|
-
# Don't clear the cache during metaschema validation!
|
41
|
-
self.class.validate!(metaschema, @base_schema.schema, {:clear_cache => false})
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
def schema_from_fragment(base_schema, fragment)
|
46
|
-
schema_uri = base_schema.uri
|
47
|
-
|
48
|
-
pointer = JSI::JSON::Pointer.from_fragment(fragment)
|
49
|
-
|
50
|
-
base_schema = JSON::Schema.new(pointer.evaluate(base_schema.schema), schema_uri, @options[:version])
|
51
|
-
|
52
|
-
if @options[:list]
|
53
|
-
base_schema.to_array_schema
|
54
|
-
elsif base_schema.is_a?(Hash)
|
55
|
-
JSON::Schema.new(base_schema, schema_uri, @options[:version])
|
56
|
-
else
|
57
|
-
base_schema
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
data/lib/jsi/json.rb
DELETED
data/lib/jsi/pathed_node.rb
DELETED
@@ -1,118 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module JSI
|
4
|
-
# including class MUST define
|
5
|
-
# - #node_document [Object] returning the document
|
6
|
-
# - #node_ptr [JSI::JSON::Pointer] returning a pointer for the node path in the document
|
7
|
-
# - #document_root_node [JSI::PathedNode] returning a PathedNode pointing at the document root
|
8
|
-
# - #parent_node [JSI::PathedNode] returning the parent node of this PathedNode
|
9
|
-
# - #deref [JSI::PathedNode] following a $ref
|
10
|
-
#
|
11
|
-
# given these, this module represents the node in the document at the path.
|
12
|
-
#
|
13
|
-
# the node content (#node_content) is the result of evaluating the node document at the path.
|
14
|
-
module PathedNode
|
15
|
-
# @return [Object] the content of this node
|
16
|
-
def node_content
|
17
|
-
content = node_ptr.evaluate(node_document)
|
18
|
-
content
|
19
|
-
end
|
20
|
-
|
21
|
-
# @yield [JSI::JSON::Pointer] if a block is given (optional), this will yield a deref'd pointer
|
22
|
-
# @return [JSI::JSON::Pointer] our node_ptr, derefed against our node_document
|
23
|
-
def node_ptr_deref(&block)
|
24
|
-
node_ptr.deref(node_document, &block)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
# module extending a {JSI::PathedNode} object when its node_content is Hash-like (responds to #to_hash)
|
29
|
-
module PathedHashNode
|
30
|
-
# yields each hash key and value of this node.
|
31
|
-
#
|
32
|
-
# each yielded key is the same as a key of the node content hash,
|
33
|
-
# and each yielded value is the result of self[key] (see #[]).
|
34
|
-
#
|
35
|
-
# returns an Enumerator if no block is given.
|
36
|
-
#
|
37
|
-
# @yield [Object, Object] each key and value of this hash node
|
38
|
-
# @return [self, Enumerator]
|
39
|
-
def each(&block)
|
40
|
-
return to_enum(__method__) { node_content_hash_pubsend(:size) } unless block_given?
|
41
|
-
if block.arity > 1
|
42
|
-
node_content_hash_pubsend(:each_key) { |k| yield k, self[k] }
|
43
|
-
else
|
44
|
-
node_content_hash_pubsend(:each_key) { |k| yield [k, self[k]] }
|
45
|
-
end
|
46
|
-
self
|
47
|
-
end
|
48
|
-
|
49
|
-
# @return [Hash] a hash in which each key is a key of the node_content hash and
|
50
|
-
# each value is the result of self[key] (see #[]).
|
51
|
-
def to_hash
|
52
|
-
{}.tap { |h| each_key { |k| h[k] = self[k] } }
|
53
|
-
end
|
54
|
-
|
55
|
-
include Hashlike
|
56
|
-
|
57
|
-
# @param method_name [String, Symbol]
|
58
|
-
# @param *a, &b are passed to the invocation of method_name
|
59
|
-
# @return [Object] the result of calling method method_name on the node_content or its #to_hash
|
60
|
-
def node_content_hash_pubsend(method_name, *a, &b)
|
61
|
-
if node_content.respond_to?(method_name)
|
62
|
-
node_content.public_send(method_name, *a, &b)
|
63
|
-
else
|
64
|
-
node_content.to_hash.public_send(method_name, *a, &b)
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
# methods that don't look at the value; can skip the overhead of #[] (invoked by #to_hash)
|
69
|
-
SAFE_KEY_ONLY_METHODS.each do |method_name|
|
70
|
-
define_method(method_name) do |*a, &b|
|
71
|
-
node_content_hash_pubsend(method_name, *a, &b)
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
module PathedArrayNode
|
77
|
-
# yields each array element of this node.
|
78
|
-
#
|
79
|
-
# each yielded element is the result of self[index] for each index of our array (see #[]).
|
80
|
-
#
|
81
|
-
# returns an Enumerator if no block is given.
|
82
|
-
#
|
83
|
-
# @yield [Object] each element of this array node
|
84
|
-
# @return [self, Enumerator]
|
85
|
-
def each
|
86
|
-
return to_enum(__method__) { node_content_ary_pubsend(:size) } unless block_given?
|
87
|
-
node_content_ary_pubsend(:each_index) { |i| yield(self[i]) }
|
88
|
-
self
|
89
|
-
end
|
90
|
-
|
91
|
-
# @return [Array] an array, the same size as the node_content, in which the
|
92
|
-
# element at each index is the result of self[index] (see #[])
|
93
|
-
def to_ary
|
94
|
-
to_a
|
95
|
-
end
|
96
|
-
|
97
|
-
include Arraylike
|
98
|
-
|
99
|
-
# @param method_name [String, Symbol]
|
100
|
-
# @param *a, &b are passed to the invocation of method_name
|
101
|
-
# @return [Object] the result of calling method method_name on the node_content or its #to_ary
|
102
|
-
def node_content_ary_pubsend(method_name, *a, &b)
|
103
|
-
if node_content.respond_to?(method_name)
|
104
|
-
node_content.public_send(method_name, *a, &b)
|
105
|
-
else
|
106
|
-
node_content.to_ary.public_send(method_name, *a, &b)
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
# methods that don't look at the value; can skip the overhead of #[] (invoked by #to_a).
|
111
|
-
# we override these methods from Arraylike
|
112
|
-
SAFE_INDEX_ONLY_METHODS.each do |method_name|
|
113
|
-
define_method(method_name) do |*a, &b|
|
114
|
-
node_content_ary_pubsend(method_name, *a, &b)
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
118
|
-
end
|