json_scanner 0.3.1 → 1.0.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.
@@ -1,360 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "spec_helper"
4
- require "json"
5
-
6
- RSpec.describe JsonScanner do
7
- it "has a version number" do
8
- expect(described_class::VERSION).not_to be_nil
9
- end
10
-
11
- it "scans json" do
12
- result = described_class.scan('["1", {"a": 2}]', [[0], [1, "a"], []])
13
- expect(result).to eq([[[1, 4, :string]], [[12, 13, :number]], [[0, 15, :array]]])
14
- expect(described_class.scan('"2"', [[]])).to eq([[[0, 3, :string]]])
15
- expect(
16
- described_class.scan("[0,1,2,3,4,5,6,7]", [[(0..2)], [(4...6)]]),
17
- ).to eq(
18
- [[[1, 2, :number], [3, 4, :number], [5, 6, :number]], [[9, 10, :number], [11, 12, :number]]],
19
- )
20
- expect(described_class.scan('{"a": 1}', [["a"], []])).to eq(
21
- [[[6, 7, :number]], [[0, 8, :object]]],
22
- )
23
- end
24
-
25
- it "supports 'symbolize_path_keys'" do
26
- expect(
27
- described_class.scan('{"a": {"b": 1}}', [[:a, "b"]], with_path: true),
28
- ).to eq([[[%w[a b], [12, 13, :number]]]])
29
- expect(
30
- described_class.scan('{"a": {"b": 1}}', [[:a, "b"]], with_path: true, symbolize_path_keys: true),
31
- ).to eq([[[%i[a b], [12, 13, :number]]]])
32
- end
33
-
34
- it "supports any key selector" do
35
- expect(
36
- described_class.scan(
37
- '[{"a":1,"b":2},{"c":3,"d":4},[5]]',
38
- [[described_class::ANY_INDEX, described_class::ANY_KEY]],
39
- ),
40
- ).to eq(
41
- [[[6, 7, :number], [12, 13, :number], [20, 21, :number], [26, 27, :number]]],
42
- )
43
- expect(
44
- described_class.scan(
45
- '{"a":[1,2],"b":{"c":3}}',
46
- [[described_class::ANY_KEY, described_class::ANY_INDEX]],
47
- ),
48
- ).to eq(
49
- [[[6, 7, :number], [8, 9, :number]]],
50
- )
51
- end
52
-
53
- it "works with max path len correctly" do
54
- expect(
55
- described_class.scan('{"a": [1]}', [[], ["a"]]),
56
- ).to eq(
57
- [[[0, 10, :object]], [[6, 9, :array]]],
58
- )
59
- expect(
60
- described_class.scan('{"a": {"b": 1}}', [[], ["a"]]),
61
- ).to eq(
62
- [[[0, 15, :object]], [[6, 14, :object]]],
63
- )
64
- expect(described_class.scan('{"a": 1}', [[]])).to eq([[[0, 8, :object]]])
65
- expect(described_class.scan("[[1]]", [[]])).to eq([[[0, 5, :array]]])
66
- expect(described_class.scan("[[1]]", [[0]])).to eq([[[1, 4, :array]]])
67
- end
68
-
69
- it "raises on invalid json" do
70
- expect do
71
- begin
72
- GC.stress = true
73
- # TODO: investigate
74
- # got "munmap_chunk(): invalid pointer" in in console once after
75
- # JsonScanner.scan '[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]', [[0,0,0,0,0,0,0]], true + Ctrl+D
76
- # (last arg wasn't handled at the time and was intended for with_path kwarg)
77
- # but I don't think it's a problem of the extension or libyajl, it happened at exit and I free everything before
78
- # `JsonScanner.scan` returns
79
- described_class.scan "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]", [[0, 0, 0, 0, 0, 0, 0]]
80
- ensure
81
- GC.stress = false
82
- end
83
- end.to raise_error described_class::ParseError
84
- end
85
-
86
- it "allows to select ranges" do
87
- expect(
88
- described_class.scan("[[1,2],[3,4]]", [[described_class::ANY_INDEX, described_class::ANY_INDEX]]),
89
- ).to eq(
90
- [[[2, 3, :number], [4, 5, :number], [8, 9, :number], [10, 11, :number]]],
91
- )
92
- expect(
93
- described_class.scan("[[1,2],[3,4]]", [[described_class::ANY_INDEX, (0...1)]]),
94
- ).to eq(
95
- [[[2, 3, :number], [8, 9, :number]]],
96
- )
97
- end
98
-
99
- it "allows only positive or -1 values" do
100
- expect do
101
- described_class.scan("[[1,2],[3,4]]", [[(0...-1)]])
102
- end.to raise_error ArgumentError
103
- expect do
104
- described_class.scan("[[1,2],[3,4]]", [[(0..-2)]])
105
- end.to raise_error ArgumentError
106
- expect do
107
- described_class.scan("[[1,2],[3,4]]", [[(-42..1)]])
108
- end.to raise_error ArgumentError
109
- end
110
-
111
- it "allows to configure error messages" do
112
- expect do
113
- described_class.scan "{1}", []
114
- end.to raise_error described_class::ParseError, /invalid object key(?!.*\(right here\))/m
115
- expect do
116
- described_class.scan "{1}", [], verbose_error: false
117
- end.to raise_error described_class::ParseError, /invalid object key(?!.*\(right here\))/m
118
- expect do
119
- described_class.scan "{1}", [], verbose_error: true
120
- end.to raise_error described_class::ParseError, /invalid object key(?=.*\(right here\))/m
121
- expect do
122
- described_class.scan("[0, 42,", [[(1..-1)]], verbose_error: true)
123
- end.to raise_error described_class::ParseError, /parse error: premature EOF.*\[0, 42,.*\(right here\) ------\^/m
124
- end
125
-
126
- it "includes bytes consumed in the exception" do
127
- expect do
128
- described_class.scan("[[1,2],,[3,4]]", [])
129
- end.to(
130
- raise_error(described_class::ParseError) do |exc|
131
- expect(exc.bytes_consumed).to eq(8)
132
- end,
133
- )
134
- expect do
135
- described_class.scan("[[1,2", [])
136
- end.to(
137
- raise_error(described_class::ParseError) do |exc|
138
- # 6 because of the final " " chunk - that's how yajl works
139
- expect(exc.bytes_consumed).to eq(6)
140
- end,
141
- )
142
- end
143
-
144
- it "allows to return an actual path to the element" do
145
- with_path_expected_res = [
146
- # result for first matcher, each element array of two items:
147
- # array of path elements and 3-element array start,end,type
148
- [[[0], [1, 6, :array]], [[1], [7, 12, :array]]],
149
- [
150
- [[0, 0], [2, 3, :number]], [[0, 1], [4, 5, :number]],
151
- [[1, 0], [8, 9, :number]], [[1, 1], [10, 11, :number]],
152
- ],
153
- ]
154
- params = [
155
- "[[1,2],[3,4]]",
156
- [
157
- [described_class::ANY_INDEX],
158
- [described_class::ANY_INDEX, described_class::ANY_INDEX],
159
- ],
160
- ]
161
- expect(described_class.scan(*params, with_path: true)).to eq(with_path_expected_res)
162
- expect(described_class.scan(*params, true)).to eq(with_path_expected_res)
163
- expect(
164
- described_class.scan(*params, false, with_path: true),
165
- ).to eq(with_path_expected_res)
166
- end
167
-
168
- it "ignores reqular flag if kwarg is given" do
169
- expect(
170
- described_class.scan(
171
- "[[1,2],[3,4]]",
172
- [
173
- [described_class::ANY_INDEX],
174
- [described_class::ANY_INDEX, described_class::ANY_INDEX],
175
- ],
176
- true, with_path: false,
177
- ),
178
- ).to eq(
179
- [
180
- # result for first matcher, each element 3-element array start,end,type
181
- [[1, 6, :array], [7, 12, :array]],
182
- [
183
- [2, 3, :number], [4, 5, :number],
184
- [8, 9, :number], [10, 11, :number],
185
- ],
186
- ],
187
- )
188
- end
189
-
190
- it "allows to pass config as a hash" do
191
- expect(
192
- described_class.scan("[1]", [[0]], { with_path: true }),
193
- ).to eq(
194
- [
195
- [[[0], [1, 2, :number]]],
196
- ],
197
- )
198
- end
199
-
200
- it "allows to configure yajl" do
201
- expect(
202
- described_class.scan("[1]____________", [[0]], { allow_trailing_garbage: true }),
203
- ).to eq([[[1, 2, :number]]])
204
- expect(
205
- described_class.scan(
206
- '["1", {"a": /* comment */ 2}]____________', [[1, "a"]],
207
- { allow_trailing_garbage: true, allow_comments: true },
208
- ),
209
- ).to eq([[[26, 27, :number]]])
210
- expect(
211
- described_class.scan(
212
- '[{"a": /* comment */ 1}]_________', [[]],
213
- { allow_comments: true, allow_trailing_garbage: true },
214
- ),
215
- ).to eq([[[0, 24, :array]]])
216
- end
217
-
218
- it "works with utf-8" do
219
- json = '{"ルビー": ["Руби"]}'.encode(Encoding::UTF_8)
220
- expect(described_class.scan(json, [[]])).to eq([[[0, json.bytesize, :object]]])
221
- res = described_class.scan(json, [["ルビー", 0]])
222
- expect(res).to eq([[[15, 25, :string]]])
223
- elem = res.first.first
224
- expect(JSON.parse(json.byteslice(elem[0]...elem[1]), quirks_mode: true)).to eq("Руби")
225
- end
226
-
227
- it "raises exceptions in utf-8" do
228
- bad_json = '{"ルビー": ["Руби" 1]}'.encode(Encoding::UTF_8)
229
- expect do
230
- described_class.scan(bad_json, [[]], verbose_error: true)
231
- # Checks encoding
232
- end.to raise_error(described_class::ParseError, Regexp.new(Regexp.escape(bad_json)))
233
- end
234
-
235
- it "works with different encodings" do
236
- # TODO: encoding validation
237
- json = '{"a": 1}'.encode(Encoding::UTF_32LE)
238
- expect do
239
- described_class.scan(json, [[]])
240
- end.to raise_error(described_class::ParseError)
241
- end
242
-
243
- context "with yajl params" do
244
- it "supports 'allow_comments'" do
245
- params = ["[0, /* answer */ 42, 0]", [[(1..-1)]]]
246
- expect(described_class.scan(*params, allow_comments: true)).to eq(
247
- [[[17, 19, :number], [21, 22, :number]]],
248
- )
249
- expect do
250
- described_class.scan(*params)
251
- end.to raise_error(described_class::ParseError)
252
- end
253
-
254
- it "supports 'dont_validate_strings'" do
255
- params = ["\"\x81\x83\"", [[]]]
256
- expect(described_class.scan(*params, dont_validate_strings: true)).to eq(
257
- [[[0, 4, :string]]],
258
- )
259
- expect do
260
- described_class.scan(*params)
261
- end.to raise_error(described_class::ParseError)
262
- params = ["{\"\x81\x83\": 42}", [[JsonScanner::ANY_KEY]]]
263
- expect(described_class.scan(*params, dont_validate_strings: true, with_path: true)).to eq(
264
- [[[["\x81\x83".dup.force_encoding(Encoding::BINARY)], [7, 9, :number]]]],
265
- )
266
- expect do
267
- described_class.scan(*params, with_path: true)
268
- end.to raise_error(described_class::ParseError)
269
- end
270
-
271
- it "supports 'allow_trailing_garbage'" do
272
- params = ["[0, 42, 0]garbage", [[(1..-1)]]]
273
- expect(described_class.scan(*params, allow_trailing_garbage: true)).to eq(
274
- [[[4, 6, :number], [8, 9, :number]]],
275
- )
276
- expect do
277
- described_class.scan(*params)
278
- end.to raise_error(described_class::ParseError)
279
- end
280
-
281
- it "supports 'allow_multiple_values'" do
282
- params = ["[0, 42, 0] [0, 34]", [[(1..-1)]]]
283
- expect(described_class.scan(*params, allow_multiple_values: true)).to eq(
284
- [[[4, 6, :number], [8, 9, :number], [16, 18, :number]]],
285
- )
286
- expect do
287
- described_class.scan(*params)
288
- end.to raise_error(described_class::ParseError)
289
- end
290
-
291
- it "handles multiple top-level values correctly with 'allow_multiple_values'" do
292
- expect(described_class.scan("[0, 42, 0] [0, 34]", [[]], allow_multiple_values: true)).to eq(
293
- [[[0, 10, :array], [12, 19, :array]]],
294
- )
295
- expect(described_class.scan('{"42": 34} [0, 34]', [[]], allow_multiple_values: true)).to eq(
296
- [[[0, 10, :object], [12, 19, :array]]],
297
- )
298
- expect(described_class.scan('[0, 42, 0] {"42": 34}', [[]], allow_multiple_values: true)).to eq(
299
- [[[0, 10, :array], [12, 22, :object]]],
300
- )
301
- expect(described_class.scan('{"42": 34} {"0": 34}', [[]], allow_multiple_values: true)).to eq(
302
- [[[0, 10, :object], [12, 21, :object]]],
303
- )
304
- end
305
-
306
- it "supports 'allow_partial_values'" do
307
- params = ["[0, 42, 0,", [[(1..-1)]]]
308
- expect(described_class.scan(*params, allow_partial_values: true)).to eq(
309
- [[[4, 6, :number], [8, 9, :number]]],
310
- )
311
- expect do
312
- described_class.scan(*params)
313
- end.to raise_error(described_class::ParseError)
314
- expect(described_class.scan("[0, 42, 0", [[(1..-1)]], allow_partial_values: true)).to eq(
315
- [[[4, 6, :number], [8, 9, :number]]],
316
- )
317
- expect(described_class.scan("[0, 42, true", [[(1..-1)]], allow_partial_values: true)).to eq(
318
- [[[4, 6, :number], [8, 12, :boolean]]],
319
- )
320
- end
321
- end
322
-
323
- describe described_class::Config do
324
- it "saves state" do
325
- key = "abracadabra".dup
326
- conf = described_class.new [[], [key]]
327
- key["cad"] = 0.chr
328
- key = nil # rubocop:disable Lint/UselessAssignment
329
- GC.start
330
- expect(
331
- 10.times.map do
332
- JsonScanner.scan '{"abracadabra": 10}', conf, with_path: true
333
- end.uniq,
334
- ).to eq([[[[[], [0, 19, :object]]], [[["abracadabra"], [16, 18, :number]]]]])
335
- expect(
336
- 10.times.map do
337
- JsonScanner.scan '{"abracadabra": 10}', conf
338
- end.uniq,
339
- ).to eq([[[[0, 19, :object]], [[16, 18, :number]]]])
340
- end
341
-
342
- it "re-raises exceptions" do
343
- expect do
344
- described_class.new [[(0...-1)]]
345
- end.to raise_error ArgumentError
346
- expect do
347
- described_class.new [[(0..-2)]]
348
- end.to raise_error ArgumentError
349
- expect do
350
- described_class.new [[(-42..1)]]
351
- end.to raise_error ArgumentError
352
- end
353
-
354
- it "supports inspect" do
355
- expect(
356
- described_class.new([[], ["abracadabra", JsonScanner::ANY_INDEX], [42, JsonScanner::ANY_KEY]]).inspect,
357
- ).to eq("#<JsonScanner::Config [[], ['abracadabra', (0..9223372036854775807)], [42, ('*'..'*')]]>")
358
- end
359
- end
360
- end
data/spec/spec_helper.rb DELETED
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "json_scanner"
4
-
5
- RSpec.configure do |config|
6
- # Enable flags like --only-failures and --next-failure
7
- config.example_status_persistence_file_path = ".rspec_status"
8
-
9
- # Disable RSpec exposing methods globally on `Module` and `main`
10
- config.disable_monkey_patching!
11
-
12
- config.expect_with :rspec do |c|
13
- c.syntax = :expect
14
- end
15
- end