jsi 0.4.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -1
  3. data/CHANGELOG.md +33 -0
  4. data/LICENSE.md +1 -1
  5. data/README.md +114 -42
  6. data/jsi.gemspec +14 -12
  7. data/lib/jsi/base/node.rb +183 -0
  8. data/lib/jsi/base.rb +388 -220
  9. data/lib/jsi/jsi_coder.rb +8 -7
  10. data/lib/jsi/metaschema.rb +0 -1
  11. data/lib/jsi/metaschema_node/bootstrap_schema.rb +101 -0
  12. data/lib/jsi/metaschema_node.rb +159 -135
  13. data/lib/jsi/ptr.rb +303 -0
  14. data/lib/jsi/schema/application/child_application/contains.rb +25 -0
  15. data/lib/jsi/schema/application/child_application/draft04.rb +22 -0
  16. data/lib/jsi/schema/application/child_application/draft06.rb +29 -0
  17. data/lib/jsi/schema/application/child_application/draft07.rb +29 -0
  18. data/lib/jsi/schema/application/child_application/items.rb +18 -0
  19. data/lib/jsi/schema/application/child_application/properties.rb +25 -0
  20. data/lib/jsi/schema/application/child_application.rb +38 -0
  21. data/lib/jsi/schema/application/draft04.rb +8 -0
  22. data/lib/jsi/schema/application/draft06.rb +8 -0
  23. data/lib/jsi/schema/application/draft07.rb +8 -0
  24. data/lib/jsi/schema/application/inplace_application/dependencies.rb +28 -0
  25. data/lib/jsi/schema/application/inplace_application/draft04.rb +26 -0
  26. data/lib/jsi/schema/application/inplace_application/draft06.rb +27 -0
  27. data/lib/jsi/schema/application/inplace_application/draft07.rb +33 -0
  28. data/lib/jsi/schema/application/inplace_application/ifthenelse.rb +20 -0
  29. data/lib/jsi/schema/application/inplace_application/ref.rb +18 -0
  30. data/lib/jsi/schema/application/inplace_application/someof.rb +44 -0
  31. data/lib/jsi/schema/application/inplace_application.rb +41 -0
  32. data/lib/jsi/schema/application.rb +12 -0
  33. data/lib/jsi/schema/draft04.rb +14 -0
  34. data/lib/jsi/schema/draft06.rb +14 -0
  35. data/lib/jsi/schema/draft07.rb +14 -0
  36. data/lib/jsi/schema/issue.rb +36 -0
  37. data/lib/jsi/schema/ref.rb +160 -0
  38. data/lib/jsi/schema/schema_ancestor_node.rb +113 -0
  39. data/lib/jsi/schema/validation/array.rb +69 -0
  40. data/lib/jsi/schema/validation/const.rb +20 -0
  41. data/lib/jsi/schema/validation/contains.rb +25 -0
  42. data/lib/jsi/schema/validation/core.rb +39 -0
  43. data/lib/jsi/schema/validation/dependencies.rb +49 -0
  44. data/lib/jsi/schema/validation/draft04/minmax.rb +91 -0
  45. data/lib/jsi/schema/validation/draft04.rb +112 -0
  46. data/lib/jsi/schema/validation/draft06.rb +122 -0
  47. data/lib/jsi/schema/validation/draft07.rb +159 -0
  48. data/lib/jsi/schema/validation/enum.rb +25 -0
  49. data/lib/jsi/schema/validation/ifthenelse.rb +46 -0
  50. data/lib/jsi/schema/validation/items.rb +54 -0
  51. data/lib/jsi/schema/validation/not.rb +20 -0
  52. data/lib/jsi/schema/validation/numeric.rb +121 -0
  53. data/lib/jsi/schema/validation/object.rb +45 -0
  54. data/lib/jsi/schema/validation/pattern.rb +34 -0
  55. data/lib/jsi/schema/validation/properties.rb +101 -0
  56. data/lib/jsi/schema/validation/property_names.rb +32 -0
  57. data/lib/jsi/schema/validation/ref.rb +40 -0
  58. data/lib/jsi/schema/validation/required.rb +27 -0
  59. data/lib/jsi/schema/validation/someof.rb +90 -0
  60. data/lib/jsi/schema/validation/string.rb +47 -0
  61. data/lib/jsi/schema/validation/type.rb +49 -0
  62. data/lib/jsi/schema/validation.rb +51 -0
  63. data/lib/jsi/schema.rb +508 -149
  64. data/lib/jsi/schema_classes.rb +199 -59
  65. data/lib/jsi/schema_registry.rb +151 -0
  66. data/lib/jsi/schema_set.rb +181 -0
  67. data/lib/jsi/simple_wrap.rb +23 -4
  68. data/lib/jsi/util/private/attr_struct.rb +127 -0
  69. data/lib/jsi/util/private.rb +204 -0
  70. data/lib/jsi/util/typelike.rb +229 -0
  71. data/lib/jsi/util.rb +89 -53
  72. data/lib/jsi/validation/error.rb +34 -0
  73. data/lib/jsi/validation/result.rb +210 -0
  74. data/lib/jsi/validation.rb +15 -0
  75. data/lib/jsi/version.rb +3 -1
  76. data/lib/jsi.rb +44 -14
  77. data/lib/schemas/json-schema.org/draft-04/schema.rb +10 -3
  78. data/lib/schemas/json-schema.org/draft-06/schema.rb +10 -3
  79. data/lib/schemas/json-schema.org/draft-07/schema.rb +14 -0
  80. data/readme.rb +138 -0
  81. data/{resources}/schemas/json-schema.org/draft-04/schema.json +149 -0
  82. data/{resources}/schemas/json-schema.org/draft-06/schema.json +154 -0
  83. data/{resources}/schemas/json-schema.org/draft-07/schema.json +168 -0
  84. metadata +75 -122
  85. data/.simplecov +0 -3
  86. data/Rakefile.rb +0 -9
  87. data/lib/jsi/base/to_rb.rb +0 -128
  88. data/lib/jsi/json/node.rb +0 -203
  89. data/lib/jsi/json/pointer.rb +0 -419
  90. data/lib/jsi/json-schema-fragments.rb +0 -61
  91. data/lib/jsi/json.rb +0 -10
  92. data/lib/jsi/pathed_node.rb +0 -118
  93. data/lib/jsi/typelike_modules.rb +0 -240
  94. data/resources/icons/AGPL-3.0.png +0 -0
  95. data/test/base_array_test.rb +0 -323
  96. data/test/base_hash_test.rb +0 -337
  97. data/test/base_test.rb +0 -486
  98. data/test/jsi_coder_test.rb +0 -85
  99. data/test/jsi_json_arraynode_test.rb +0 -150
  100. data/test/jsi_json_hashnode_test.rb +0 -132
  101. data/test/jsi_json_node_test.rb +0 -257
  102. data/test/jsi_json_pointer_test.rb +0 -102
  103. data/test/jsi_test.rb +0 -11
  104. data/test/jsi_typelike_as_json_test.rb +0 -53
  105. data/test/metaschema_node_test.rb +0 -19
  106. data/test/schema_module_test.rb +0 -21
  107. data/test/schema_test.rb +0 -208
  108. data/test/spreedly_openapi_test.rb +0 -8
  109. data/test/test_helper.rb +0 -97
  110. data/test/util_test.rb +0 -62
@@ -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
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module JSI
4
- module JSON
5
- autoload :Node, 'jsi/json/node'
6
- autoload :ArrayNode, 'jsi/json/node'
7
- autoload :HashNode, 'jsi/json/node'
8
- autoload :Pointer, 'jsi/json/pointer'
9
- end
10
- end
@@ -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