json_scanner 0.3.0 → 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,352 +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
- end
135
-
136
- it "allows to return an actual path to the element" do
137
- with_path_expected_res = [
138
- # result for first matcher, each element array of two items:
139
- # array of path elements and 3-element array start,end,type
140
- [[[0], [1, 6, :array]], [[1], [7, 12, :array]]],
141
- [
142
- [[0, 0], [2, 3, :number]], [[0, 1], [4, 5, :number]],
143
- [[1, 0], [8, 9, :number]], [[1, 1], [10, 11, :number]],
144
- ],
145
- ]
146
- params = [
147
- "[[1,2],[3,4]]",
148
- [
149
- [described_class::ANY_INDEX],
150
- [described_class::ANY_INDEX, described_class::ANY_INDEX],
151
- ],
152
- ]
153
- expect(described_class.scan(*params, with_path: true)).to eq(with_path_expected_res)
154
- expect(described_class.scan(*params, true)).to eq(with_path_expected_res)
155
- expect(
156
- described_class.scan(*params, false, with_path: true),
157
- ).to eq(with_path_expected_res)
158
- end
159
-
160
- it "ignores reqular flag if kwarg is given" do
161
- expect(
162
- described_class.scan(
163
- "[[1,2],[3,4]]",
164
- [
165
- [described_class::ANY_INDEX],
166
- [described_class::ANY_INDEX, described_class::ANY_INDEX],
167
- ],
168
- true, with_path: false,
169
- ),
170
- ).to eq(
171
- [
172
- # result for first matcher, each element 3-element array start,end,type
173
- [[1, 6, :array], [7, 12, :array]],
174
- [
175
- [2, 3, :number], [4, 5, :number],
176
- [8, 9, :number], [10, 11, :number],
177
- ],
178
- ],
179
- )
180
- end
181
-
182
- it "allows to pass config as a hash" do
183
- expect(
184
- described_class.scan("[1]", [[0]], { with_path: true }),
185
- ).to eq(
186
- [
187
- [[[0], [1, 2, :number]]],
188
- ],
189
- )
190
- end
191
-
192
- it "allows to configure yajl" do
193
- expect(
194
- described_class.scan("[1]____________", [[0]], { allow_trailing_garbage: true }),
195
- ).to eq([[[1, 2, :number]]])
196
- expect(
197
- described_class.scan(
198
- '["1", {"a": /* comment */ 2}]____________', [[1, "a"]],
199
- { allow_trailing_garbage: true, allow_comments: true },
200
- ),
201
- ).to eq([[[26, 27, :number]]])
202
- expect(
203
- described_class.scan(
204
- '[{"a": /* comment */ 1}]_________', [[]],
205
- { allow_comments: true, allow_trailing_garbage: true },
206
- ),
207
- ).to eq([[[0, 24, :array]]])
208
- end
209
-
210
- it "works with utf-8" do
211
- json = '{"ルビー": ["Руби"]}'.encode(Encoding::UTF_8)
212
- expect(described_class.scan(json, [[]])).to eq([[[0, json.bytesize, :object]]])
213
- res = described_class.scan(json, [["ルビー", 0]])
214
- expect(res).to eq([[[15, 25, :string]]])
215
- elem = res.first.first
216
- expect(JSON.parse(json.byteslice(elem[0]...elem[1]), quirks_mode: true)).to eq("Руби")
217
- end
218
-
219
- it "raises exceptions in utf-8" do
220
- bad_json = '{"ルビー": ["Руби" 1]}'.encode(Encoding::UTF_8)
221
- expect do
222
- described_class.scan(bad_json, [[]], verbose_error: true)
223
- # Checks encoding
224
- end.to raise_error(described_class::ParseError, Regexp.new(Regexp.escape(bad_json)))
225
- end
226
-
227
- it "works with different encodings" do
228
- # TODO: encoding validation
229
- json = '{"a": 1}'.encode(Encoding::UTF_32LE)
230
- expect do
231
- described_class.scan(json, [[]])
232
- end.to raise_error(described_class::ParseError)
233
- end
234
-
235
- context "with yajl params" do
236
- it "supports 'allow_comments'" do
237
- params = ["[0, /* answer */ 42, 0]", [[(1..-1)]]]
238
- expect(described_class.scan(*params, allow_comments: true)).to eq(
239
- [[[17, 19, :number], [21, 22, :number]]],
240
- )
241
- expect do
242
- described_class.scan(*params)
243
- end.to raise_error(described_class::ParseError)
244
- end
245
-
246
- it "supports 'dont_validate_strings'" do
247
- params = ["\"\x81\x83\"", [[]]]
248
- expect(described_class.scan(*params, dont_validate_strings: true)).to eq(
249
- [[[0, 4, :string]]],
250
- )
251
- expect do
252
- described_class.scan(*params)
253
- end.to raise_error(described_class::ParseError)
254
- params = ["{\"\x81\x83\": 42}", [[JsonScanner::ANY_KEY]]]
255
- expect(described_class.scan(*params, dont_validate_strings: true, with_path: true)).to eq(
256
- [[[["\x81\x83".dup.force_encoding(Encoding::BINARY)], [7, 9, :number]]]],
257
- )
258
- expect do
259
- described_class.scan(*params, with_path: true)
260
- end.to raise_error(described_class::ParseError)
261
- end
262
-
263
- it "supports 'allow_trailing_garbage'" do
264
- params = ["[0, 42, 0]garbage", [[(1..-1)]]]
265
- expect(described_class.scan(*params, allow_trailing_garbage: true)).to eq(
266
- [[[4, 6, :number], [8, 9, :number]]],
267
- )
268
- expect do
269
- described_class.scan(*params)
270
- end.to raise_error(described_class::ParseError)
271
- end
272
-
273
- it "supports 'allow_multiple_values'" do
274
- params = ["[0, 42, 0] [0, 34]", [[(1..-1)]]]
275
- expect(described_class.scan(*params, allow_multiple_values: true)).to eq(
276
- [[[4, 6, :number], [8, 9, :number], [16, 18, :number]]],
277
- )
278
- expect do
279
- described_class.scan(*params)
280
- end.to raise_error(described_class::ParseError)
281
- end
282
-
283
- it "handles multiple top-level values correctly with 'allow_multiple_values'" do
284
- expect(described_class.scan("[0, 42, 0] [0, 34]", [[]], allow_multiple_values: true)).to eq(
285
- [[[0, 10, :array], [12, 19, :array]]],
286
- )
287
- expect(described_class.scan('{"42": 34} [0, 34]', [[]], allow_multiple_values: true)).to eq(
288
- [[[0, 10, :object], [12, 19, :array]]],
289
- )
290
- expect(described_class.scan('[0, 42, 0] {"42": 34}', [[]], allow_multiple_values: true)).to eq(
291
- [[[0, 10, :array], [12, 22, :object]]],
292
- )
293
- expect(described_class.scan('{"42": 34} {"0": 34}', [[]], allow_multiple_values: true)).to eq(
294
- [[[0, 10, :object], [12, 21, :object]]],
295
- )
296
- end
297
-
298
- it "supports 'allow_partial_values'" do
299
- params = ["[0, 42, 0,", [[(1..-1)]]]
300
- expect(described_class.scan(*params, allow_partial_values: true)).to eq(
301
- [[[4, 6, :number], [8, 9, :number]]],
302
- )
303
- expect do
304
- described_class.scan(*params)
305
- end.to raise_error(described_class::ParseError)
306
- expect(described_class.scan("[0, 42, 0", [[(1..-1)]], allow_partial_values: true)).to eq(
307
- [[[4, 6, :number], [8, 9, :number]]],
308
- )
309
- expect(described_class.scan("[0, 42, true", [[(1..-1)]], allow_partial_values: true)).to eq(
310
- [[[4, 6, :number], [8, 12, :boolean]]],
311
- )
312
- end
313
- end
314
-
315
- describe described_class::Config do
316
- it "saves state" do
317
- key = "abracadabra".dup
318
- conf = described_class.new [[], [key]]
319
- key["cad"] = 0.chr
320
- key = nil # rubocop:disable Lint/UselessAssignment
321
- GC.start
322
- expect(
323
- 10.times.map do
324
- JsonScanner.scan '{"abracadabra": 10}', conf, with_path: true
325
- end.uniq,
326
- ).to eq([[[[[], [0, 19, :object]]], [[["abracadabra"], [16, 18, :number]]]]])
327
- expect(
328
- 10.times.map do
329
- JsonScanner.scan '{"abracadabra": 10}', conf
330
- end.uniq,
331
- ).to eq([[[[0, 19, :object]], [[16, 18, :number]]]])
332
- end
333
-
334
- it "re-raises exceptions" do
335
- expect do
336
- described_class.new [[(0...-1)]]
337
- end.to raise_error ArgumentError
338
- expect do
339
- described_class.new [[(0..-2)]]
340
- end.to raise_error ArgumentError
341
- expect do
342
- described_class.new [[(-42..1)]]
343
- end.to raise_error ArgumentError
344
- end
345
-
346
- it "supports inspect" do
347
- expect(
348
- described_class.new([[], ["abracadabra", JsonScanner::ANY_INDEX], [42, JsonScanner::ANY_KEY]]).inspect,
349
- ).to eq("#<JsonScanner::Config [[], ['abracadabra', (0..9223372036854775807)], [42, ('*'..'*')]]>")
350
- end
351
- end
352
- 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