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/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
|