jsi 0.2.0 → 0.6.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 +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/json/pointer.rb
DELETED
@@ -1,330 +0,0 @@
|
|
1
|
-
require 'addressable/uri'
|
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
|
-
def self.from_fragment(fragment)
|
49
|
-
fragment = Addressable::URI.unescape(fragment)
|
50
|
-
match = fragment.match(/\A#/)
|
51
|
-
if match
|
52
|
-
from_pointer(match.post_match, type: 'fragment')
|
53
|
-
else
|
54
|
-
raise(PointerSyntaxError, "Invalid fragment syntax in #{fragment.inspect}: fragment must begin with #")
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
# parse a pointer string and instantiate as a JSI::JSON::Pointer
|
59
|
-
#
|
60
|
-
# ptr1 = JSI::JSON::Pointer.from_pointer('/foo')
|
61
|
-
# => #<JSI::JSON::Pointer pointer: /foo>
|
62
|
-
# ptr1.reference_tokens
|
63
|
-
# => ["foo"]
|
64
|
-
#
|
65
|
-
# ptr2 = JSI::JSON::Pointer.from_pointer('/foo~0bar/baz~1qux')
|
66
|
-
# => #<JSI::JSON::Pointer pointer: /foo~0bar/baz~1qux>
|
67
|
-
# ptr2.reference_tokens
|
68
|
-
# => ["foo~bar", "baz/qux"]
|
69
|
-
#
|
70
|
-
# @param pointer_string [String] a pointer string
|
71
|
-
# @param type (for internal use) indicates the original representation of the pointer
|
72
|
-
# @return [JSI::JSON::Pointer]
|
73
|
-
def self.from_pointer(pointer_string, type: 'pointer')
|
74
|
-
tokens = pointer_string.split('/', -1).map! do |piece|
|
75
|
-
piece.gsub('~1', '/').gsub('~0', '~')
|
76
|
-
end
|
77
|
-
if tokens[0] == ''
|
78
|
-
new(tokens[1..-1], type: type)
|
79
|
-
elsif tokens.empty?
|
80
|
-
new(tokens, type: type)
|
81
|
-
else
|
82
|
-
raise(PointerSyntaxError, "Invalid pointer syntax in #{pointer_string.inspect}: pointer must begin with /")
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
# initializes a JSI::JSON::Pointer from the given reference_tokens.
|
87
|
-
#
|
88
|
-
# @param reference_tokens [Array<Object>]
|
89
|
-
# @param type [String, Symbol] one of 'pointer' or 'fragment'
|
90
|
-
def initialize(reference_tokens, type: nil)
|
91
|
-
unless reference_tokens.respond_to?(:to_ary)
|
92
|
-
raise(TypeError, "reference_tokens must be an array. got: #{reference_tokens.inspect}")
|
93
|
-
end
|
94
|
-
@reference_tokens = reference_tokens.to_ary.map(&:freeze).freeze
|
95
|
-
@type = type.is_a?(Symbol) ? type.to_s : type
|
96
|
-
end
|
97
|
-
|
98
|
-
attr_reader :reference_tokens
|
99
|
-
|
100
|
-
# takes a root json document and evaluates this pointer through the document, returning the value
|
101
|
-
# pointed to by this pointer.
|
102
|
-
#
|
103
|
-
# @param document [#to_ary, #to_hash] the document against which we will evaluate this pointer
|
104
|
-
# @return [Object] the content of the document pointed to by this pointer
|
105
|
-
# @raise [JSI::JSON::Pointer::ReferenceError] the document does not contain the path this pointer references
|
106
|
-
def evaluate(document)
|
107
|
-
res = reference_tokens.inject(document) do |value, token|
|
108
|
-
if value.respond_to?(:to_ary)
|
109
|
-
if token.is_a?(String) && token =~ /\A\d|[1-9]\d+\z/
|
110
|
-
token = token.to_i
|
111
|
-
end
|
112
|
-
unless token.is_a?(Integer)
|
113
|
-
raise(ReferenceError, "Invalid resolution for #{to_s}: #{token.inspect} is not an integer and cannot be resolved in array #{value.inspect}")
|
114
|
-
end
|
115
|
-
unless (0...(value.respond_to?(:size) ? value : value.to_ary).size).include?(token)
|
116
|
-
raise(ReferenceError, "Invalid resolution for #{to_s}: #{token.inspect} is not a valid index of #{value.inspect}")
|
117
|
-
end
|
118
|
-
(value.respond_to?(:[]) ? value : value.to_ary)[token]
|
119
|
-
elsif value.respond_to?(:to_hash)
|
120
|
-
unless (value.respond_to?(:key?) ? value : value.to_hash).key?(token)
|
121
|
-
raise(ReferenceError, "Invalid resolution for #{to_s}: #{token.inspect} is not a valid key of #{value.inspect}")
|
122
|
-
end
|
123
|
-
(value.respond_to?(:[]) ? value : value.to_hash)[token]
|
124
|
-
else
|
125
|
-
raise(ReferenceError, "Invalid resolution for #{to_s}: #{token.inspect} cannot be resolved in #{value.inspect}")
|
126
|
-
end
|
127
|
-
end
|
128
|
-
res
|
129
|
-
end
|
130
|
-
|
131
|
-
# @return [String] the pointer string representation of this Pointer
|
132
|
-
def pointer
|
133
|
-
reference_tokens.map { |t| '/' + t.to_s.gsub('~', '~0').gsub('/', '~1') }.join('')
|
134
|
-
end
|
135
|
-
|
136
|
-
# @return [String] the fragment string representation of this Pointer
|
137
|
-
def fragment
|
138
|
-
'#' + Addressable::URI.escape(pointer)
|
139
|
-
end
|
140
|
-
|
141
|
-
# @return [Boolean] whether this pointer points to the root (has an empty array of reference_tokens)
|
142
|
-
def root?
|
143
|
-
reference_tokens.empty?
|
144
|
-
end
|
145
|
-
|
146
|
-
# @return [JSI::JSON::Pointer] pointer to the parent of where this pointer points
|
147
|
-
# @raise [JSI::JSON::Pointer::ReferenceError] if this pointer has no parent (points to the root)
|
148
|
-
def parent
|
149
|
-
if root?
|
150
|
-
raise(ReferenceError, "cannot access parent of root pointer: #{pretty_inspect.chomp}")
|
151
|
-
else
|
152
|
-
Pointer.new(reference_tokens[0...-1], type: @type)
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
# @return [Boolean] does this pointer contain the other_ptr - that is, is this pointer an
|
157
|
-
# ancestor of other_ptr, a child pointer. contains? is inclusive; a pointer does contain itself.
|
158
|
-
def contains?(other_ptr)
|
159
|
-
self.reference_tokens == other_ptr.reference_tokens[0...self.reference_tokens.size]
|
160
|
-
end
|
161
|
-
|
162
|
-
# @return [JSI::JSON::Pointer] returns this pointer relative to the given ancestor_ptr
|
163
|
-
# @raise [JSI::JSON::Pointer::ReferenceError] if the given ancestor_ptr is not an ancestor of this pointer
|
164
|
-
def ptr_relative_to(ancestor_ptr)
|
165
|
-
unless ancestor_ptr.contains?(self)
|
166
|
-
raise(ReferenceError, "ancestor_ptr #{ancestor_ptr.inspect} is not ancestor of #{inspect}")
|
167
|
-
end
|
168
|
-
Pointer.new(reference_tokens[ancestor_ptr.reference_tokens.size..-1], type: @type)
|
169
|
-
end
|
170
|
-
|
171
|
-
# @param ptr [JSI::JSON::Pointer]
|
172
|
-
# @return [JSI::JSON::Pointer] a pointer with the reference tokens of this one plus the given ptr's.
|
173
|
-
def +(ptr)
|
174
|
-
unless ptr.is_a?(JSI::JSON::Pointer)
|
175
|
-
raise(TypeError, "ptr must be a JSI::JSON::Pointer; got: #{ptr.inspect}")
|
176
|
-
end
|
177
|
-
Pointer.new(reference_tokens + ptr.reference_tokens, type: @type)
|
178
|
-
end
|
179
|
-
|
180
|
-
# @param n [Integer]
|
181
|
-
# @return [JSI::JSON::Pointer] a Pointer consisting of the first n of our reference_tokens
|
182
|
-
# @raise [ArgumentError] if n is not between 0 and the size of our reference_tokens
|
183
|
-
def take(n)
|
184
|
-
unless (0..reference_tokens.size).include?(n)
|
185
|
-
raise(ArgumentError, "n not in range (0..#{reference_tokens.size}): #{n.inspect}")
|
186
|
-
end
|
187
|
-
Pointer.new(reference_tokens.take(n), type: @type)
|
188
|
-
end
|
189
|
-
|
190
|
-
# appends the given token to this Pointer's reference tokens and returns the result
|
191
|
-
#
|
192
|
-
# @param token [Object]
|
193
|
-
# @return [JSI::JSON::Pointer] pointer to a child node of this pointer with the given token
|
194
|
-
def [](token)
|
195
|
-
Pointer.new(reference_tokens + [token], type: @type)
|
196
|
-
end
|
197
|
-
|
198
|
-
# takes a document and a block. the block is yielded the content of the given document at this
|
199
|
-
# pointer's location. the block must result a modified copy of that content (and MUST NOT modify
|
200
|
-
# the object it is given). this modified copy of that content is incorporated into a modified copy
|
201
|
-
# of the given document, which is then returned. the structure and contents of the document outside
|
202
|
-
# the path pointed to by this pointer is not modified.
|
203
|
-
#
|
204
|
-
# @param document [Object] the document to apply this pointer to
|
205
|
-
# @yield [Object] the content this pointer applies to in the given document
|
206
|
-
# the block must result in the new content which will be placed in the modified document copy.
|
207
|
-
# @return [Object] a copy of the given document, with the content this pointer applies to
|
208
|
-
# replaced by the result of the block
|
209
|
-
def modified_document_copy(document, &block)
|
210
|
-
# we need to preserve the rest of the document, but modify the content at our path.
|
211
|
-
#
|
212
|
-
# this is actually a bit tricky. we can't modify the original document, obviously.
|
213
|
-
# we could do a deep copy, but that's expensive. instead, we make a copy of each array
|
214
|
-
# or hash in the path above this node. this node's content is modified by the caller, and
|
215
|
-
# that is recursively merged up to the document root. the recursion is done with a
|
216
|
-
# y combinator, for no other reason than that was a fun way to implement it.
|
217
|
-
modified_document = JSI::Util.ycomb do |rec|
|
218
|
-
proc do |subdocument, subpath|
|
219
|
-
if subpath == []
|
220
|
-
Typelike.modified_copy(subdocument, &block)
|
221
|
-
else
|
222
|
-
car = subpath[0]
|
223
|
-
cdr = subpath[1..-1]
|
224
|
-
if subdocument.respond_to?(:to_hash)
|
225
|
-
subdocument_car = (subdocument.respond_to?(:[]) ? subdocument : subdocument.to_hash)[car]
|
226
|
-
car_object = rec.call(subdocument_car, cdr)
|
227
|
-
if car_object.object_id == subdocument_car.object_id
|
228
|
-
subdocument
|
229
|
-
else
|
230
|
-
(subdocument.respond_to?(:merge) ? subdocument : subdocument.to_hash).merge({car => car_object})
|
231
|
-
end
|
232
|
-
elsif subdocument.respond_to?(:to_ary)
|
233
|
-
if car.is_a?(String) && car =~ /\A\d+\z/
|
234
|
-
car = car.to_i
|
235
|
-
end
|
236
|
-
unless car.is_a?(Integer)
|
237
|
-
raise(TypeError, "bad subscript #{car.pretty_inspect.chomp} with remaining subpath: #{cdr.inspect} for array: #{subdocument.pretty_inspect.chomp}")
|
238
|
-
end
|
239
|
-
subdocument_car = (subdocument.respond_to?(:[]) ? subdocument : subdocument.to_ary)[car]
|
240
|
-
car_object = rec.call(subdocument_car, cdr)
|
241
|
-
if car_object.object_id == subdocument_car.object_id
|
242
|
-
subdocument
|
243
|
-
else
|
244
|
-
(subdocument.respond_to?(:[]=) ? subdocument : subdocument.to_ary).dup.tap do |arr|
|
245
|
-
arr[car] = car_object
|
246
|
-
end
|
247
|
-
end
|
248
|
-
else
|
249
|
-
raise(TypeError, "bad subscript: #{car.pretty_inspect.chomp} with remaining subpath: #{cdr.inspect} for content: #{subdocument.pretty_inspect.chomp}")
|
250
|
-
end
|
251
|
-
end
|
252
|
-
end
|
253
|
-
end.call(document, reference_tokens)
|
254
|
-
modified_document
|
255
|
-
end
|
256
|
-
|
257
|
-
# if this Pointer points at a $ref node within the given document, #deref attempts
|
258
|
-
# to follow that $ref and return a Pointer to the referenced location. otherwise,
|
259
|
-
# this Pointer is returned.
|
260
|
-
#
|
261
|
-
# if the content this Pointer points to in the document is not hash-like, does not
|
262
|
-
# have a $ref property, its $ref cannot be found, or its $ref points outside the document,
|
263
|
-
# this pointer is returned.
|
264
|
-
#
|
265
|
-
# @param document [Object] the document this pointer applies to
|
266
|
-
# @yield [Pointer] if a block is given (optional), this will yield a deref'd pointer. if this
|
267
|
-
# pointer does not point to a $ref object in the given document, the block is not called.
|
268
|
-
# if we point to a $ref which cannot be followed (e.g. a $ref to an external
|
269
|
-
# document, which is not yet supported), the block is not called.
|
270
|
-
# @return [Pointer] dereferenced pointer, or this pointer
|
271
|
-
def deref(document, &block)
|
272
|
-
block ||= Util::NOOP
|
273
|
-
content = evaluate(document)
|
274
|
-
|
275
|
-
if content.respond_to?(:to_hash)
|
276
|
-
ref = (content.respond_to?(:[]) ? content : content.to_hash)['$ref']
|
277
|
-
end
|
278
|
-
return self unless ref.is_a?(String)
|
279
|
-
|
280
|
-
if ref[/\A#/]
|
281
|
-
return Pointer.from_fragment(ref).tap(&block)
|
282
|
-
end
|
283
|
-
|
284
|
-
# HAX for how google does refs and ids
|
285
|
-
if document['schemas'].respond_to?(:to_hash)
|
286
|
-
if document['schemas'][ref]
|
287
|
-
return Pointer.new(['schemas', ref], type: 'hax').tap(&block)
|
288
|
-
end
|
289
|
-
document['schemas'].each do |k, schema|
|
290
|
-
if schema['id'] == ref
|
291
|
-
return Pointer.new(['schemas', k], type: 'hax').tap(&block)
|
292
|
-
end
|
293
|
-
end
|
294
|
-
end
|
295
|
-
|
296
|
-
#raise(NotImplementedError, "cannot dereference #{ref}") # TODO
|
297
|
-
return self
|
298
|
-
end
|
299
|
-
|
300
|
-
# @return [String] string representation of this Pointer
|
301
|
-
def inspect
|
302
|
-
"#<#{self.class.inspect} #{representation_s}>"
|
303
|
-
end
|
304
|
-
|
305
|
-
# @return [String] string representation of this Pointer
|
306
|
-
def to_s
|
307
|
-
inspect
|
308
|
-
end
|
309
|
-
|
310
|
-
# pointers are equal if the reference_tokens are equal, regardless of @type
|
311
|
-
def fingerprint
|
312
|
-
{class: JSI::JSON::Pointer, reference_tokens: reference_tokens}
|
313
|
-
end
|
314
|
-
include FingerprintHash
|
315
|
-
|
316
|
-
private
|
317
|
-
|
318
|
-
# @return [String] a representation of this pointer based on @type
|
319
|
-
def representation_s
|
320
|
-
if @type == 'fragment'
|
321
|
-
"fragment: #{fragment}"
|
322
|
-
elsif @type == 'pointer'
|
323
|
-
"pointer: #{pointer}"
|
324
|
-
else
|
325
|
-
"reference_tokens: #{reference_tokens.inspect}"
|
326
|
-
end
|
327
|
-
end
|
328
|
-
end
|
329
|
-
end
|
330
|
-
end
|
@@ -1,59 +0,0 @@
|
|
1
|
-
require "json-schema"
|
2
|
-
|
3
|
-
# apply the changes from https://github.com/ruby-json-schema/json-schema/pull/382
|
4
|
-
|
5
|
-
# json-schema/validator.rb
|
6
|
-
|
7
|
-
module JSON
|
8
|
-
class Validator
|
9
|
-
def initialize(schema_data, data, opts={})
|
10
|
-
@options = @@default_opts.clone.merge(opts)
|
11
|
-
@errors = []
|
12
|
-
|
13
|
-
validator = self.class.validator_for_name(@options[:version])
|
14
|
-
@options[:version] = validator
|
15
|
-
@options[:schema_reader] ||= self.class.schema_reader
|
16
|
-
|
17
|
-
@validation_options = @options[:record_errors] ? {:record_errors => true} : {}
|
18
|
-
@validation_options[:insert_defaults] = true if @options[:insert_defaults]
|
19
|
-
@validation_options[:strict] = true if @options[:strict] == true
|
20
|
-
@validation_options[:clear_cache] = true if !@@cache_schemas || @options[:clear_cache]
|
21
|
-
|
22
|
-
@@mutex.synchronize { @base_schema = initialize_schema(schema_data) }
|
23
|
-
@original_data = data
|
24
|
-
@data = initialize_data(data)
|
25
|
-
@@mutex.synchronize { build_schemas(@base_schema) }
|
26
|
-
|
27
|
-
# If the :fragment option is set, try and validate against the fragment
|
28
|
-
if opts[:fragment]
|
29
|
-
@base_schema = schema_from_fragment(@base_schema, opts[:fragment])
|
30
|
-
end
|
31
|
-
|
32
|
-
# validate the schema, if requested
|
33
|
-
if @options[:validate_schema]
|
34
|
-
if @base_schema.schema["$schema"]
|
35
|
-
base_validator = self.class.validator_for_name(@base_schema.schema["$schema"])
|
36
|
-
end
|
37
|
-
metaschema = base_validator ? base_validator.metaschema : validator.metaschema
|
38
|
-
# Don't clear the cache during metaschema validation!
|
39
|
-
self.class.validate!(metaschema, @base_schema.schema, {:clear_cache => false})
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
def schema_from_fragment(base_schema, fragment)
|
44
|
-
schema_uri = base_schema.uri
|
45
|
-
|
46
|
-
pointer = JSI::JSON::Pointer.from_fragment(fragment)
|
47
|
-
|
48
|
-
base_schema = JSON::Schema.new(pointer.evaluate(base_schema.schema), schema_uri, @options[:version])
|
49
|
-
|
50
|
-
if @options[:list]
|
51
|
-
base_schema.to_array_schema
|
52
|
-
elsif base_schema.is_a?(Hash)
|
53
|
-
JSON::Schema.new(base_schema, schema_uri, @options[:version])
|
54
|
-
else
|
55
|
-
base_schema
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
data/lib/jsi/json.rb
DELETED
data/test/base_array_test.rb
DELETED
@@ -1,209 +0,0 @@
|
|
1
|
-
require_relative 'test_helper'
|
2
|
-
|
3
|
-
describe JSI::BaseArray do
|
4
|
-
let(:document) do
|
5
|
-
['foo', {'lamp' => [3]}, ['q', 'r']]
|
6
|
-
end
|
7
|
-
let(:path) { [] }
|
8
|
-
let(:pointer) { JSI::JSON::Pointer.new(path) }
|
9
|
-
let(:instance) { JSI::JSON::Node.new_by_type(document, pointer) }
|
10
|
-
let(:schema_content) do
|
11
|
-
{
|
12
|
-
'type' => 'array',
|
13
|
-
'items' => [
|
14
|
-
{'type' => 'string'},
|
15
|
-
{'type' => 'object', 'items' => {}},
|
16
|
-
{'type' => 'array'},
|
17
|
-
],
|
18
|
-
}
|
19
|
-
end
|
20
|
-
let(:schema) { JSI::Schema.new(schema_content) }
|
21
|
-
let(:class_for_schema) { JSI.class_for_schema(schema) }
|
22
|
-
let(:subject) { class_for_schema.new(instance) }
|
23
|
-
|
24
|
-
describe '#[] with a default that is a basic type' do
|
25
|
-
let(:schema_content) do
|
26
|
-
{
|
27
|
-
'type' => 'array',
|
28
|
-
'items' => {'default' => 'foo'},
|
29
|
-
}
|
30
|
-
end
|
31
|
-
describe 'default value' do
|
32
|
-
let(:document) { [1] }
|
33
|
-
it 'returns the default value' do
|
34
|
-
assert_equal('foo', subject[2])
|
35
|
-
end
|
36
|
-
end
|
37
|
-
describe 'nondefault value (basic type)' do
|
38
|
-
let(:document) { ['who'] }
|
39
|
-
it 'returns the nondefault value' do
|
40
|
-
assert_equal('who', subject[0])
|
41
|
-
end
|
42
|
-
end
|
43
|
-
describe 'nondefault value (nonbasic type)' do
|
44
|
-
let(:document) { [[2]] }
|
45
|
-
it 'returns the nondefault value' do
|
46
|
-
assert_instance_of(JSI.class_for_schema(schema['items']), subject[0])
|
47
|
-
assert_equal([2], subject[0].as_json)
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
describe '#[] with a default that is a nonbasic type' do
|
52
|
-
let(:schema_content) do
|
53
|
-
{
|
54
|
-
'type' => 'array',
|
55
|
-
'items' => {'default' => {'foo' => 2}},
|
56
|
-
}
|
57
|
-
end
|
58
|
-
describe 'default value' do
|
59
|
-
let(:document) { [{'bar' => 3}] }
|
60
|
-
it 'returns the default value' do
|
61
|
-
assert_instance_of(JSI.class_for_schema(schema['items']), subject[1])
|
62
|
-
assert_equal({'foo' => 2}, subject[1].as_json)
|
63
|
-
end
|
64
|
-
end
|
65
|
-
describe 'nondefault value (basic type)' do
|
66
|
-
let(:document) { [true, 'who'] }
|
67
|
-
it 'returns the nondefault value' do
|
68
|
-
assert_equal('who', subject[1])
|
69
|
-
end
|
70
|
-
end
|
71
|
-
describe 'nondefault value (nonbasic type)' do
|
72
|
-
let(:document) { [true, [2]] }
|
73
|
-
it 'returns the nondefault value' do
|
74
|
-
assert_instance_of(JSI.class_for_schema(schema['items']), subject[1])
|
75
|
-
assert_equal([2], subject[1].as_json)
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
describe 'arraylike []=' do
|
80
|
-
it 'sets an index' do
|
81
|
-
orig_2 = subject[2]
|
82
|
-
|
83
|
-
subject[2] = {'y' => 'z'}
|
84
|
-
|
85
|
-
assert_equal({'y' => 'z'}, subject[2].as_json)
|
86
|
-
assert_instance_of(JSI.class_for_schema(schema.schema_node['items'][2]), orig_2)
|
87
|
-
assert_instance_of(JSI.class_for_schema(schema.schema_node['items'][2]), subject[2])
|
88
|
-
end
|
89
|
-
it 'modifies the instance, visible to other references to the same instance' do
|
90
|
-
orig_instance = subject.instance
|
91
|
-
|
92
|
-
subject[2] = {'y' => 'z'}
|
93
|
-
|
94
|
-
assert_equal(orig_instance, subject.instance)
|
95
|
-
assert_equal({'y' => 'z'}, orig_instance[2].as_json)
|
96
|
-
assert_equal({'y' => 'z'}, subject.instance[2].as_json)
|
97
|
-
assert_equal(orig_instance.class, subject.instance.class)
|
98
|
-
end
|
99
|
-
describe 'when the instance is not arraylike' do
|
100
|
-
let(:instance) { nil }
|
101
|
-
it 'errors' do
|
102
|
-
err = assert_raises(NoMethodError) { subject[2] = 0 }
|
103
|
-
assert_match(%r(\Aundefined method `\[\]=' for #<JSI::SchemaClasses::X.*>\z), err.message)
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
107
|
-
# these methods just delegate to Array so not going to test excessively
|
108
|
-
describe 'index only methods' do
|
109
|
-
it('#each_index') { assert_equal([0, 1, 2], subject.each_index.to_a) }
|
110
|
-
it('#empty?') { assert_equal(false, subject.empty?) }
|
111
|
-
it('#length') { assert_equal(3, subject.length) }
|
112
|
-
it('#size') { assert_equal(3, subject.size) }
|
113
|
-
end
|
114
|
-
describe 'index + element methods' do
|
115
|
-
it('#|') { assert_equal(['foo', subject[1], subject[2], 0], subject | [0]) }
|
116
|
-
it('#&') { assert_equal(['foo'], subject & ['foo']) }
|
117
|
-
it('#*') { assert_equal(subject.to_a, subject * 1) }
|
118
|
-
it('#+') { assert_equal(subject.to_a, subject + []) }
|
119
|
-
it('#-') { assert_equal([subject[1], subject[2]], subject - ['foo']) }
|
120
|
-
it('#<=>') { assert_equal(1, subject <=> []) }
|
121
|
-
it('#<=>') { assert_equal(-1, [] <=> subject) }
|
122
|
-
require 'abbrev'
|
123
|
-
it('#abbrev') { assert_equal({'a' => 'a'}, class_for_schema.new(['a']).abbrev) }
|
124
|
-
it('#assoc') { assert_equal(['q', 'r'], subject.assoc('q')) }
|
125
|
-
it('#at') { assert_equal('foo', subject.at(0)) }
|
126
|
-
it('#bsearch') { assert_equal(nil, subject.bsearch { false }) }
|
127
|
-
it('#bsearch_index') { assert_equal(nil, subject.bsearch_index { false }) } if [].respond_to?(:bsearch_index)
|
128
|
-
it('#combination') { assert_equal([['foo'], [subject[1]], [subject[2]]], subject.combination(1).to_a) }
|
129
|
-
it('#count') { assert_equal(1, subject.count('foo')) }
|
130
|
-
it('#cycle') { assert_equal(subject.to_a, subject.cycle(1).to_a) }
|
131
|
-
it('#dig') { assert_equal(3, subject.dig(1, 'lamp', 0)) } if [].respond_to?(:dig)
|
132
|
-
it('#drop') { assert_equal([subject[2]], subject.drop(2)) }
|
133
|
-
it('#drop_while') { assert_equal([subject[1], subject[2]], subject.drop_while { |e| e == 'foo' }) }
|
134
|
-
it('#fetch') { assert_equal('foo', subject.fetch(0)) }
|
135
|
-
it('#find_index') { assert_equal(0, subject.find_index { true }) }
|
136
|
-
it('#first') { assert_equal('foo', subject.first) }
|
137
|
-
it('#include?') { assert_equal(true, subject.include?('foo')) }
|
138
|
-
it('#index') { assert_equal(0, subject.index('foo')) }
|
139
|
-
it('#join') { assert_equal('a b', class_for_schema.new(['a', 'b']).join(' ')) }
|
140
|
-
it('#last') { assert_equal(subject[2], subject.last) }
|
141
|
-
it('#pack') { assert_equal(' ', class_for_schema.new([32]).pack('c')) }
|
142
|
-
it('#permutation') { assert_equal([['foo'], [subject[1]], [subject[2]]], subject.permutation(1).to_a) }
|
143
|
-
it('#product') { assert_equal([], subject.product([])) }
|
144
|
-
# due to differences in implementation between #assoc and #rassoc, the reason for which
|
145
|
-
# I cannot begin to fathom, assoc works but rassoc does not because rassoc has different
|
146
|
-
# type checking than assoc for the array(like) array elements.
|
147
|
-
# compare:
|
148
|
-
# assoc: https://github.com/ruby/ruby/blob/v2_5_0/array.c#L3780-L3813
|
149
|
-
# rassoc: https://github.com/ruby/ruby/blob/v2_5_0/array.c#L3815-L3847
|
150
|
-
# for this reason, rassoc is NOT defined on Arraylike and #content must be called.
|
151
|
-
it('#rassoc') { assert_equal(['q', 'r'], subject.instance.content.rassoc('r')) }
|
152
|
-
it('#repeated_combination') { assert_equal([[]], subject.repeated_combination(0).to_a) }
|
153
|
-
it('#repeated_permutation') { assert_equal([[]], subject.repeated_permutation(0).to_a) }
|
154
|
-
it('#reverse') { assert_equal([subject[2], subject[1], 'foo'], subject.reverse) }
|
155
|
-
it('#reverse_each') { assert_equal([subject[2], subject[1], 'foo'], subject.reverse_each.to_a) }
|
156
|
-
it('#rindex') { assert_equal(0, subject.rindex('foo')) }
|
157
|
-
it('#rotate') { assert_equal([subject[1], subject[2], 'foo'], subject.rotate) }
|
158
|
-
it('#sample') { assert_equal('a', class_for_schema.new(['a']).sample) }
|
159
|
-
it('#shelljoin') { assert_equal('a', class_for_schema.new(['a']).shelljoin) } if [].respond_to?(:shelljoin)
|
160
|
-
it('#shuffle') { assert_equal(3, subject.shuffle.size) }
|
161
|
-
it('#slice') { assert_equal(['foo'], subject.slice(0, 1)) }
|
162
|
-
it('#sort') { assert_equal(['a'], class_for_schema.new(['a']).sort) }
|
163
|
-
it('#take') { assert_equal(['foo'], subject.take(1)) }
|
164
|
-
it('#take_while') { assert_equal([], subject.take_while { false }) }
|
165
|
-
it('#transpose') { assert_equal([], class_for_schema.new([]).transpose) }
|
166
|
-
it('#uniq') { assert_equal(subject.to_a, subject.uniq) }
|
167
|
-
it('#values_at') { assert_equal(['foo'], subject.values_at(0)) }
|
168
|
-
it('#zip') { assert_equal([['foo', 'foo'], [subject[1], subject[1]], [subject[2], subject[2]]], subject.zip(subject)) }
|
169
|
-
end
|
170
|
-
describe 'with an instance that has to_ary but not other ary instance methods' do
|
171
|
-
let(:instance) { SortOfArray.new(['foo', {'lamp' => SortOfArray.new([3])}, SortOfArray.new(['q', 'r'])]) }
|
172
|
-
describe 'delegating instance methods to #to_ary' do
|
173
|
-
it('#each_index') { assert_equal([0, 1, 2], subject.each_index.to_a) }
|
174
|
-
it('#size') { assert_equal(3, subject.size) }
|
175
|
-
it('#count') { assert_equal(1, subject.count('foo')) }
|
176
|
-
it('#slice') { assert_equal(['foo'], subject.slice(0, 1)) }
|
177
|
-
it('#[]') { assert_equal(SortOfArray.new(['q', 'r']), subject[2].instance) }
|
178
|
-
it('#as_json') { assert_equal(['foo', {'lamp' => [3]}, ['q', 'r']], subject.as_json) }
|
179
|
-
end
|
180
|
-
end
|
181
|
-
describe 'modified copy methods' do
|
182
|
-
it('#reject') { assert_equal(class_for_schema.new(JSI::JSON::ArrayNode.new(['foo'], pointer)), subject.reject { |e| e != 'foo' }) }
|
183
|
-
it('#reject block var') do
|
184
|
-
subj_a = subject.to_a
|
185
|
-
subject.reject { |e| assert_equal(e, subj_a.shift) }
|
186
|
-
end
|
187
|
-
it('#select') { assert_equal(class_for_schema.new(JSI::JSON::ArrayNode.new(['foo'], pointer)), subject.select { |e| e == 'foo' }) }
|
188
|
-
it('#select block var') do
|
189
|
-
subj_a = subject.to_a
|
190
|
-
subject.select { |e| assert_equal(e, subj_a.shift) }
|
191
|
-
end
|
192
|
-
it('#compact') { assert_equal(subject, subject.compact) }
|
193
|
-
describe 'at a depth' do
|
194
|
-
let(:document) { [['b', 'q'], {'c' => ['d', 'e']}] }
|
195
|
-
let(:path) { ['1', 'c'] }
|
196
|
-
it('#select') do
|
197
|
-
selected = subject.select { |e| e == 'd' }
|
198
|
-
equivalent_node = JSI::JSON::ArrayNode.new([['b', 'q'], {'c' => ['d']}], pointer)
|
199
|
-
equivalent = class_for_schema.new(equivalent_node)
|
200
|
-
assert_equal(equivalent, selected)
|
201
|
-
end
|
202
|
-
end
|
203
|
-
end
|
204
|
-
JSI::Arraylike::DESTRUCTIVE_METHODS.each do |destructive_method_name|
|
205
|
-
it("does not respond to destructive method #{destructive_method_name}") do
|
206
|
-
assert(!subject.respond_to?(destructive_method_name))
|
207
|
-
end
|
208
|
-
end
|
209
|
-
end
|