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