nosj 0.5.0 → 0.5.1
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/CHANGELOG.md +10 -0
- data/Cargo.lock +1 -1
- data/ext/nosj/Cargo.toml +1 -1
- data/ext/nosj/fuzz/src/prelude.rb +33 -6
- data/ext/nosj/src/patch.rs +20 -6
- data/lib/nosj/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f6c2ec4836b21fafff5a18244b4089e632b39ed4dd48cdbb57d9ab4529beea73
|
|
4
|
+
data.tar.gz: 544ce5c54ab3d6c4300b720d6462af4f02f3427868a6fa94b5cc960b9d2704f4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 777e339c9ab64479dad1811b33da20bd30e545afdc390a32b70ac1c92c1574cfd7f7c04a5c70f87dd3d1469520970387c50db68af3b3b18c849d3705c92274ee
|
|
7
|
+
data.tar.gz: 8a099e1880b3b6efd6f28311636fbd4e29fb4aca4c3a8c19e5e4603c5fed3cd1936e53547fde34821e701eaf09b77dcc83f44317bd9af4d29d0fe307bc85ea52
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
## [0.5.1] - 2026-10-06
|
|
2
|
+
|
|
3
|
+
- Fixed: `NOSJ.patch` read a `path` without the leading slash (`"a"`
|
|
4
|
+
instead of `"/a"`) as the root pointer, so `add`, `copy`, and `move`
|
|
5
|
+
silently replaced the whole document with the value instead of
|
|
6
|
+
raising, and `remove` reported `cannot remove the root`. Such paths
|
|
7
|
+
now raise the same `ArgumentError` as `NOSJ.at_pointer`
|
|
8
|
+
(`JSON Pointer must be empty or start with '/'`) in every op. The
|
|
9
|
+
bug dates back to 0.3.0. Found by fuzzing.
|
|
10
|
+
|
|
1
11
|
## [0.5.0] - 2026-09-25
|
|
2
12
|
|
|
3
13
|
**json 3.0 compatibility.** nosj now matches the behavior of the json
|
data/Cargo.lock
CHANGED
data/ext/nosj/Cargo.toml
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# required by lib/nosj.rb as "nosj/nosj" (Init_nosj set explicitly in
|
|
6
6
|
# lib.rs). Gem name, module, and API are plain nosj.
|
|
7
7
|
name = "nosj_native"
|
|
8
|
-
version = "0.5.
|
|
8
|
+
version = "0.5.1"
|
|
9
9
|
edition = "2021"
|
|
10
10
|
authors = ["Yaroslav Markin <yaroslav@markin.net>"]
|
|
11
11
|
license = "MIT"
|
|
@@ -140,11 +140,19 @@ module NOSJFuzz
|
|
|
140
140
|
raise "yielded values diverge from per-line parses" unless yielded.eql?(reference)
|
|
141
141
|
|
|
142
142
|
if status == :ok && !yielded.empty?
|
|
143
|
-
#
|
|
144
|
-
#
|
|
145
|
-
|
|
143
|
+
# Parse refuses lone surrogates, the only unencodable strings, but
|
|
144
|
+
# an overflow float (1e999, 124.4e044754) parses to Infinity, which
|
|
145
|
+
# generates only under allow_nan; it lifts exactly that refusal,
|
|
146
|
+
# and the round-trip then reads Infinity back under it too.
|
|
147
|
+
opts = nil
|
|
148
|
+
ndjson = begin
|
|
149
|
+
NOSJ.generate_lines_native(yielded, nil)
|
|
150
|
+
rescue NOSJ::GeneratorError
|
|
151
|
+
opts = {allow_nan: true}
|
|
152
|
+
stage("generate_lines with allow_nan") { NOSJ.generate_lines_native(yielded, opts) }
|
|
153
|
+
end
|
|
146
154
|
back = []
|
|
147
|
-
NOSJ.each_line_native(ndjson,
|
|
155
|
+
NOSJ.each_line_native(ndjson, opts) { |v| back << v }
|
|
148
156
|
raise "generate_lines does not round-trip" unless back.eql?(yielded)
|
|
149
157
|
end
|
|
150
158
|
nil
|
|
@@ -227,6 +235,7 @@ module NOSJFuzz
|
|
|
227
235
|
# --- the pure-Ruby reference implementations ------------------------
|
|
228
236
|
|
|
229
237
|
def ref_splice(tree, edits)
|
|
238
|
+
edits.each_value { |value| generable(value) }
|
|
230
239
|
token_lists = edits.keys.map { |ptr| ptr_tokens(ptr) }
|
|
231
240
|
token_lists.combination(2) do |a, b|
|
|
232
241
|
short, long = (a.length <= b.length) ? [a, b] : [b, a]
|
|
@@ -262,8 +271,8 @@ module NOSJFuzz
|
|
|
262
271
|
path = op["path"]
|
|
263
272
|
raise RefFail unless path.is_a?(String)
|
|
264
273
|
case name
|
|
265
|
-
when "add" then ref_add(box, ptr_tokens(path), fetch_value(op))
|
|
266
|
-
when "replace" then ref_replace(box, ptr_tokens(path), fetch_value(op))
|
|
274
|
+
when "add" then ref_add(box, ptr_tokens(path), generable(fetch_value(op)))
|
|
275
|
+
when "replace" then ref_replace(box, ptr_tokens(path), generable(fetch_value(op)))
|
|
267
276
|
when "remove" then ref_remove(box, ptr_tokens(path))
|
|
268
277
|
when "move"
|
|
269
278
|
from = op["from"]
|
|
@@ -366,6 +375,24 @@ module NOSJFuzz
|
|
|
366
375
|
op["value"]
|
|
367
376
|
end
|
|
368
377
|
|
|
378
|
+
# splice, add, and replace generate their values with default options,
|
|
379
|
+
# which refuse non-finite floats, and an overflow literal in the spec
|
|
380
|
+
# (1e999) parses to Infinity. test compares, and move/copy splice raw
|
|
381
|
+
# document bytes, so a 1e999 already in the document passes there.
|
|
382
|
+
def generable(value)
|
|
383
|
+
raise RefFail unless finite_floats?(value)
|
|
384
|
+
value
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
def finite_floats?(v)
|
|
388
|
+
case v
|
|
389
|
+
when Float then v.finite?
|
|
390
|
+
when Array then v.all? { |e| finite_floats?(e) }
|
|
391
|
+
when Hash then v.each_value.all? { |e| finite_floats?(e) }
|
|
392
|
+
else true
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
|
|
369
396
|
def deep_dup(v)
|
|
370
397
|
case v
|
|
371
398
|
when Hash then v.transform_values { |e| deep_dup(e) }
|
data/ext/nosj/src/patch.rs
CHANGED
|
@@ -119,11 +119,25 @@ fn container_children(
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
/// Split an RFC 6901 pointer into (parent, unescaped last token).
|
|
122
|
-
/// Returns None for the root pointer ""
|
|
123
|
-
|
|
124
|
-
|
|
122
|
+
/// Returns None for the root pointer "" only: any other pointer must
|
|
123
|
+
/// start with '/', else the crate's own InvalidPointer error raises as
|
|
124
|
+
/// ArgumentError, exactly like `span_at`. (A slash-free path once read
|
|
125
|
+
/// as the root, and `add` replaced the whole document.)
|
|
126
|
+
fn split_pointer<'p>(ruby: &Ruby, pointer: &'p str) -> Result<Option<(&'p str, String)>, Error> {
|
|
127
|
+
if pointer.is_empty() {
|
|
128
|
+
return Ok(None);
|
|
129
|
+
}
|
|
130
|
+
if !pointer.starts_with('/') {
|
|
131
|
+
// Byte 0 is where the crate reports a missing leading slash.
|
|
132
|
+
let invalid = nosj::ParseError {
|
|
133
|
+
offset: 0,
|
|
134
|
+
kind: nosj::ErrorKind::InvalidPointer,
|
|
135
|
+
};
|
|
136
|
+
return Err(arg_error(ruby, invalid.to_string()));
|
|
137
|
+
}
|
|
138
|
+
let cut = pointer.rfind('/').unwrap_or(0);
|
|
125
139
|
let token = pointer[cut + 1..].replace("~1", "/").replace("~0", "~");
|
|
126
|
-
Some((&pointer[..cut], token))
|
|
140
|
+
Ok(Some((&pointer[..cut], token)))
|
|
127
141
|
}
|
|
128
142
|
|
|
129
143
|
/// RFC 6902 array index token: digits only, no leading zeros.
|
|
@@ -211,7 +225,7 @@ fn op_add(
|
|
|
211
225
|
value: &Insert,
|
|
212
226
|
cfg: &GenConfig,
|
|
213
227
|
) -> Result<Vec<u8>, Error> {
|
|
214
|
-
let Some((parent, token)) = split_pointer(path) else {
|
|
228
|
+
let Some((parent, token)) = split_pointer(ruby, path)? else {
|
|
215
229
|
// Root: the value becomes the entire document.
|
|
216
230
|
return apply_edit(ruby, doc, 0, doc.len(), value, cfg);
|
|
217
231
|
};
|
|
@@ -295,7 +309,7 @@ fn extend_insert(
|
|
|
295
309
|
/// RFC 6902 `remove`: the entry disappears along with its key and one
|
|
296
310
|
/// separating comma; surrounding formatting stays.
|
|
297
311
|
fn op_remove(ruby: &Ruby, doc: &[u8], path: &str) -> Result<Vec<u8>, Error> {
|
|
298
|
-
let Some((parent, token)) = split_pointer(path) else {
|
|
312
|
+
let Some((parent, token)) = split_pointer(ruby, path)? else {
|
|
299
313
|
return Err(patch_error(ruby, "cannot remove the root".into()));
|
|
300
314
|
};
|
|
301
315
|
let Some((pstart, pend)) = span_at(ruby, doc, parent)? else {
|
data/lib/nosj/version.rb
CHANGED