jsi 0.2.0 → 0.6.0

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