jsi 0.2.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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