feat: Buffer readDiscard - #7244
Conversation
Signed-off-by: turuslan <turuslan.devbox@gmail.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #7244 +/- ##
=======================================
Coverage 95.02% 95.03%
=======================================
Files 618 619 +1
Lines 51496 51506 +10
Branches 9300 9301 +1
=======================================
+ Hits 48936 48947 +11
Misses 2021 2021
+ Partials 539 538 -1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
bartlomieju
left a comment
There was a problem hiding this comment.
Thanks for the PR, and for the clear motivation — the buf.readSync({ byteLength: n, set() {} }) workaround is genuinely awkward, and a bytes.Buffer.Next(n)-style consume is a reasonable thing to want.
The blocker isn't the code, it's the venue. @std/io is in wind-down: io/mod.ts states that the Reader/Writer interfaces are deprecated in Deno and that these utilities are deprecated along with them, and the module's recent history is almost entirely removals (BufReader, BufWriter, readDelim, MultiReader). Adding public surface to a module we're winding down is a policy call, so I'd like a second maintainer opinion before we take this. Please don't take that as a rejection — it's a question about @std/io rather than about your change.
If we do take it, I've left inline notes on a few things that should be fixed first. The NaN one is the important one — it silently corrupts the buffer.
One more, on naming: readDiscard doesn't match anything else on the class (truncate, reset, bytes, empty). Since you invited suggestions — discard(n) or skip(n) reads better to me.
| * @param n The number of bytes to discard. | ||
| */ | ||
| readDiscard(n: number) { | ||
| if (n < 0 || n > this.length) { |
There was a problem hiding this comment.
This guard misses non-integers. readDiscard(NaN) passes both comparisons — NaN < 0 and NaN > this.length are both false — so #off becomes NaN permanently, which makes length NaN and silently corrupts the buffer with no error. Same for fractional n.
| if (n < 0 || n > this.length) { | |
| if (!Number.isInteger(n) || n < 0 || n > this.length) { |
| */ | ||
| readDiscard(n: number) { | ||
| if (n < 0 || n > this.length) { | ||
| throw new Error("Buffer read discard out of range"); |
There was a problem hiding this comment.
Two things here, per the error-message style guide in CONTRIBUTING.md:
- The message is passive and state-based rather than action-based, and it omits the offending value — the reader can't tell what
nwas or what range was expected. RangeErroris the right type for an out-of-range argument. (truncate()above uses bareError, but that's pre-existing and new code shouldn't copy it.)
| throw new Error("Buffer read discard out of range"); | |
| throw new RangeError( | |
| `Cannot discard bytes as "n" must be an integer >= 0 and <= ${this.length}: received ${n}`, | |
| ); |
| * assertEquals(buf.length, 10); | ||
| * ``` | ||
| * | ||
| * @param n The number of bytes to discard. |
There was a problem hiding this comment.
The prose above documents that this throws, so it should carry a @throws tag to match the rest of the module:
| * @param n The number of bytes to discard. | |
| * @param n The number of bytes to discard. | |
| * @throws {RangeError} If `n` is not an integer, is negative, or exceeds the buffer length. |
| writeAllSync(reader, bytes); | ||
| assertEquals(reader.bytes(), bytes.subarray(0, 4)); | ||
|
|
||
| assertThrows(() => reader.readDiscard(5)); |
There was a problem hiding this comment.
This asserts that something throws, but pins neither the error class nor the message — so it would still pass if the failure mode changed entirely. Repo tests normally pin both, e.g. assertThrows(() => reader.readDiscard(5), RangeError, "Cannot discard bytes").
Could you also cover the edge cases? Negative n, n === 0, discarding from an empty buffer, and a non-integer such as NaN (see my note on the guard — that one currently passes silently).
Signed-off-by: turuslan <turuslan.devbox@gmail.com>
Bufferclass is used by parsers.Sometimes parser needs to advance read position without copying bytes.
For example, zero-copy parser peeks available
bytes(), then consumes some of them and advances read position.Bufferclass hides read position as#offprivate field.The only function that advances read position is
read(p)/readSync(p), which accept buffer as argument.Go provides https://pkg.go.dev/bytes#Buffer.Next function, which advances read position without copying bytes.
This PR proposes
readDiscard(n)(please suggest better name), which doesn't return value.There is
truncate(n)function, which does opposite, it keeps "first N bytes", not "after N bytes"