jsi 0.4.0 → 0.7.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 +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
|