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.
- checksums.yaml +4 -4
- data/ext/json_scanner/extconf.rb +7 -1
- data/ext/json_scanner/json_scanner.c +247 -83
- data/lib/json_scanner/version.rb +1 -1
- data/lib/json_scanner.rb +86 -1
- metadata +17 -8
- data/README.md +0 -166
- data/spec/extensiontesttask.rb +0 -128
- data/spec/json_scanner_spec.c +0 -0
- data/spec/json_scanner_spec.rb +0 -360
- data/spec/spec_helper.rb +0 -15
data/spec/json_scanner_spec.rb
DELETED
@@ -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
|