Skip to content

feat(jsonrpc): clearer errors for malformed JSON and invalid params#3702

Open
RafaelGranza wants to merge 15 commits into
mainfrom
granza/json-pretty-print
Open

feat(jsonrpc): clearer errors for malformed JSON and invalid params#3702
RafaelGranza wants to merge 15 commits into
mainfrom
granza/json-pretty-print

Conversation

@RafaelGranza
Copy link
Copy Markdown
Contributor

@RafaelGranza RafaelGranza commented Jun 4, 2026

Context:

Related to Issue #3581

Before, a malformed JSON request returned only the raw decoder message.
Errors now point at what's wrong.

Also, long lines are truncated to 80 cols, so the marker stays aligned.

Errors it Covers

  • syntax errors
  • unexpected EOF
  • type mismatches
  • trailing commas.
  • missing non-optional param field
  • missing/unexpected params in list

Examples:

Errors ate python styled.

{"id":@}
      ^
unexpected '@', expected a value [line 1, column 7]
...xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" z}
                                                                           ^
unexpected 'z', expected ',' or '}' [line 1, column 230]
{"jsonrpc": 5, "method": "x", "id": 1}
            ^
field "jsonrpc" should be string, got number [line 1, column 13]

Benchmark:

We no longer copy the whole stream, we only keep the last 512 bytes read. So the overhead stays roughly constant instead of growing with the request size.

Large requests, main (no buffering) vs this PR:

main (no copy) this PR delta
time 1MB ~14.9 ms/op ~15.1 ms/op noise
time 10MB ~151 ms/op ~153 ms/op noise
mem 1MB ~9.45 MB/op ~9.42 MB/op noise
mem 10MB ~77.7 MB/op ~79.3 MB/op ~+1.6 MB
allocs 1MB ~50 /op ~53 /op +3 /op
allocs 10MB ~52 /op ~57 /op +5 /op

SONIC

When SONIC arrives it won't break.
Its errors already carry position, so it will just work, only not printed as nicely until we add a case for it (see the TODO in errorOffset).

Comment thread jsonrpc/server.go Outdated
Comment thread jsonrpc/server.go Outdated
Comment thread jsonrpc/pretty_error.go Outdated
Comment thread jsonrpc/pretty_error.go Outdated
@codecov
Copy link
Copy Markdown

codecov Bot commented Jun 4, 2026

Codecov Report

❌ Patch coverage is 93.70629% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 76.69%. Comparing base (9de7658) to head (a903f34).

Files with missing lines Patch % Lines
jsonrpc/pretty_error.go 93.54% 6 Missing and 2 partials ⚠️
jsonrpc/server.go 94.73% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3702      +/-   ##
==========================================
+ Coverage   76.67%   76.69%   +0.02%     
==========================================
  Files         409      410       +1     
  Lines       37055    37181     +126     
==========================================
+ Hits        28411    28517     +106     
- Misses       6668     6681      +13     
- Partials     1976     1983       +7     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread jsonrpc/server.go Outdated
Copy link
Copy Markdown
Contributor

@rodrodros rodrodros left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Half done PR. Yes now more information is given to the user, but it is given in such way that is not human friendly. Let's improve upon that.

You can look into other RPCs error and get inspiration from those.

Comment thread jsonrpc/server_test.go
Comment thread jsonrpc/server_test.go Outdated
Comment thread jsonrpc/pretty_error.go Outdated
@RafaelGranza
Copy link
Copy Markdown
Contributor Author

Here are some of the new errors:
*There are no colors since the error can be used outside go and shell contexts.

{]
 ^
unexpected ']', expected a string key or '}' [line 1, column 2]
{"a" 1}
     ^
unexpected '1', expected ':' [line 1, column 6]
{"a":}
     ^
unexpected '}', expected a value [line 1, column 6]
{"a":1, }
        ^
unexpected trailing comma before '}' [line 1, column 9]
[1,2,]
     ^
unexpected trailing comma before ']' [line 1, column 6]
{"id":@}
      ^
unexpected '@', expected a value [line 1, column 7]
{"jsonrpc": "2.0", "method": "method", "id": 1
                                              ^
unexpected end of input [line 1, column 47]
{"a": "unterminated
                   ^
unexpected end of input [line 1, column 20]
{"jsonrpc": 5, "method": "x", "id": 1}
            ^
field "jsonrpc" should be string, got number [line 1, column 13]
^
unexpected end of input [line 1, column 1]
  {]
   ^
unexpected ']', expected a string key or '}' [line 3, column 4]
...xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" z}
                                                                           ^
unexpected 'z', expected ',' or '}' [line 1, column 230]
{@yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy...
 ^
unexpected '@', expected a string key or '}' [line 1, column 2]

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 5, 2026

Claude finished @RafaelGranza's task in 2m 46s —— View job


Re-review: 767fe8a5b — Python-like error format

  • Read latest commit & changed files
  • Validate offset/column/marker math
  • Check edge cases (panics, EOF, batch, windowing)
  • Build + go vet (tests need approval — see note)

This commit does two things, both clean:

  1. Type-aware dispatchdescribeError now switches on the concrete error via errors.As/errors.Is (*json.UnmarshalTypeError → friendly field "jsonrpc" should be string, got number, EOF → unexpected end of input, *json.SyntaxError → token jargon mapping), replacing the older nested string-matching. Cleaner and more robust.
  2. Format change — drops the indented |_ msg at line X column Y for the flatter Python-style msg [line X, column Y], matching the examples you posted. The caret line is unchanged.

Verified

  • Build passes, go vet ./jsonrpc/ clean.
  • Type-error path: {"jsonrpc": 5, ...}UnmarshalTypeError{Field:"jsonrpc", Type:string, Value:number}, offset 13-1=12 → caret under 5, column 13. ✓
  • Trailing-comma path still routes through precededByComma: {"a":1, }unexpected trailing comma before '}' [line 1, column 9]. ✓
  • EOF path: empty input → offset=len(input)=0offendingLine returns "", caret at column 1, unexpected end of input [line 1, column 1]. ✓
  • Windowing (truncateAround, 80-col): right-windowed (column 230) and start-of-line (column 2) cases keep the caret aligned to the ellipsis offset; the message now sits on its own line at column 0, so it's no longer affected by markerCol. ✓
  • No panic on untrusted input: offset is bounded above by len(input) (and ≥ 0 in practice), so input[:offset] / input[offset:] slices stay safe.
  • Typo fixed: mazSizeOfMessagemaxLineWidth. ✓

Nit (non-blocking)

  • describeError's default branch is now unreachable. prettyParseError only calls it after parseErrorOffset returned ok — which is true only for *json.SyntaxError, *json.UnmarshalTypeError, or io.EOF/io.ErrUnexpectedEOF, exactly the three cases the switch already handles. Harmless defensive code, but the default: return err.Error() can't fire today. (describeSyntaxError's own !ok fallback is the real fallback path.)

Carried-over follow-ups (not required here)

  • Per-element batch decode still uses raw err.Error() (handleBatchRequest, server.go) — a malformed individual request inside a batch won't get the new marker.
  • parseErrorOffset dropped the lower-bound clamp (min not max(0, min(...))) — unreachable today since stdlib offsets are ≥ 1, as you noted, but worth re-adding once the SONIC path feeds offsets through here.
  • isBatch + >bufferSize leading whitespacePeek(n) returns ErrBufferFull → routed through the single-request path (still parses, just not flagged as batch). Pathological only.

Note: I couldn't run go test ./jsonrpc/ in this environment (requires approval), but go build and go vet pass and I traced each table case by hand — CI's table tests should confirm.

No blockers — this round lands the human-friendly error UX the issue asked for.
granza/json-pretty-print

@NethermindEth NethermindEth deleted a comment from github-actions Bot Jun 5, 2026
@NethermindEth NethermindEth deleted a comment from github-actions Bot Jun 5, 2026
@NethermindEth NethermindEth deleted a comment from github-actions Bot Jun 5, 2026
@NethermindEth NethermindEth deleted a comment from github-actions Bot Jun 5, 2026
@NethermindEth NethermindEth deleted a comment from github-actions Bot Jun 5, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 5, 2026

Claude finished @RafaelGranza's task in 2m 5s —— View job


Re-review: 90384530d — error-marker UX + extra tests

  • Read latest commits & all changed files
  • Re-verify offset/column/marker math + windowing
  • Check panics / EOF / batch / leading-whitespace edge cases
  • go build ./jsonrpc/ ✓ (tests need approval in this env — traced table cases by hand)

This round lands the human-friendly format rodrodros asked for, plus a solid table of edge cases. The implementation looks correct and well-bounded — no blockers.

Verified

  • Marker alignment holds for left-window (column 2), right-window (column 230), and EOF-past-end cases — truncateAround returns idx - start + len(left) + 1, which lands the caret one past the last rune for unexpected end of input. ✓
  • No panic on untrusted input. offset is upper-bounded by len(input); EOF returns len(input); input[:offset]/input[offset:] stay in range, and empty input → offset=0"\n^\n…". ✓
  • Leading-whitespace position bug is fixed. isBatch now only Peeks (no Discard) and runs after the TeeReader is attached, so leading newlines reach errorRecoverBuffer and stay aligned with the decoder offset — confirmed by the "leading blank line keeps line number" case (line 2). ✓
  • Column is now rune-counted (utf8.RuneCount), addressing the earlier bytes-vs-runes nit. ✓
  • Untranslatable syntax errors ({"a":truX}) degrade gracefully to the raw stdlib message, still wrapped with line/column + marker. ✓
  • lll lint exemption is correctly scoped to server_test.go only.

Nits (non-blocking)

  • Per-element batch decode still emits raw err.Error()server.go:404-409 (handleBatchRequest). A malformed individual request inside an otherwise-valid batch skips prettyParseError, so it gets the old terse message while the outer batch error is pretty-printed. Not a clean 1:1 swap (the json.RawMessage is already sub-sliced, so offsets are element-relative), so deferring is fine — but a // TODO here would stop it reading as an oversight.
  • Top-level type mismatch leaks the Go type namepretty_error.go:67: a bare 5 body yields expected jsonrpc.Request, got number (see the "top-level type mismatch" test). expected a JSON object reads better for an external client. (Posted inline.)

Carried-over (already discussed, fine to leave)

  • describeError's default branch and the dropped lower-bound clamp in parseErrorOffset are both unreachable today; worth re-adding the max(0, …) clamp once the SONIC path feeds offsets through here, as you noted in the TODO.

Nice work on the benchmark + body-size-bounded copy justification — the success-path cost is well characterized.

Note: go test ./jsonrpc/ requires approval in this environment, so I traced each new table case by hand against the code rather than running CI's tests; go build and the manual trace agree on every expected string.
granza/json-pretty-print

Comment thread jsonrpc/pretty_error.go Outdated
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 5, 2026

Claude finished @RafaelGranza's task in 1m 7s —— View job


Re-review: 8391934b2 — polish parse error messages

  • Read latest commit & changed files
  • Re-verify offset/column/marker math + windowing
  • Check panics / EOF / batch / leading-whitespace edge cases
  • Build + vet (tests need approval in this env — traced by hand)

This commit is a single-line polish that resolves the last open nit, plus the matching test update. Clean.

Verified

  • Top-level type-mismatch leak is fixed. describeTypeError now returns expected a JSON object, got number instead of expected jsonrpc.Request, got number for the Field == "" case (pretty_error.go:67). Traced the 5 body: UnmarshalTypeError.Offset=1offset=0line 1, column 1, caret under 55\n^\nexpected a JSON object, got number [line 1, column 1], which exactly matches the updated "top-level type mismatch" test. ✓
  • The wording is accurate, not just nicer. Top-level type errors only reach this branch on the single-request path (isBatch routes [-prefixed input to the []json.RawMessage decode, whose elements are RawMessage and never mismatch), so the decode target is always Request — an object. "expected a JSON object" is correct, not a white lie. ✓
  • No regressions in the surrounding paths — describeTypeError's Field != "" branch (field "jsonrpc" should be string, got number), the syntax-error/trailing-comma/EOF dispatch, windowing, and rune-counted columns are all unchanged from the prior round. Build + go vet ./jsonrpc/ pass.

Carried-over (already discussed — fine to leave)

  • Per-element batch decode still emits raw err.Error() (server.go:404-409); deferring is reasonable since the sub-sliced RawMessage offsets are element-relative.
  • describeError's default branch and the dropped max(0, …) lower-bound clamp in parseErrorOffset are both unreachable today; worth re-adding the clamp when the SONIC path starts feeding offsets through, per your TODO.

No blockers. This lands the human-friendly error UX the issue asked for, and the last reviewer nit is now addressed.

Note: go test ./jsonrpc/ requires approval in this environment, so I traced the changed table case against the code by hand; go build + go vet pass and agree with the expected string.
granza/json-pretty-print

@RafaelGranza RafaelGranza self-assigned this Jun 5, 2026
Copy link
Copy Markdown
Contributor

@rodrodros rodrodros left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, thank you!

Before merging, could you please swap the column which makes sense for us programmers but might not be for non-programmers. My suggestion is position or similar.

Finally, in the tests, can you please add at least one real Starknet request (multi line) with an error. The examples I saw in the unit tests are limited to 1 to 2 lines.

@RafaelGranza RafaelGranza force-pushed the granza/json-pretty-print branch from b479925 to 1cbb5e9 Compare June 5, 2026 17:49
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 5, 2026

Claude finished @RafaelGranza's task in 3m 25s —— View job


Re-review: 1cbb5e9b8 — horizontal trim + sliding-window error buffer

  • Read latest commits & changed files
  • Verify window/offset/column math + sliding-window panics
  • Confirm reviewer requests addressed (columnposition, multi-line Starknet test)
  • Build + go vet (tests need approval in this env — traced table cases by hand)

This round adds the bounded sliding-window buffer (windowBuffer, 512 B) and 80-col horizontal trimming, and resolves the two pre-merge asks from the approving review. No blockers.

Verified

  • Both pre-merge requests are done. columnposition everywhere (pretty_error.go:205 + all table cases), and there's now a real multi-line starknet_getStorageAt request with a missing comma (server_test.go:274-282, caret on line 4, position 39) plus a 202-line "error past the captured window" case. ✓
  • Sliding-window math is panic-safe on untrusted input. absOffset is min(offset, consumedBytes), markerPos = max(0, absOffset - windowStart), and windowStart = consumedBytes - len(window), so markerPos ∈ [0, len(window)]. Every slice (window[:markerPos], window[markerPos:], offendingLine) stays in range, including markerPos == len(window) (EOF) and empty input. ✓
  • Line number stays accurate after the window slidesnewlinesSeen - count(window[markerPos:], '\n') + 1 counts newlines before the marker against the full-stream total, not just what's left in the window. Confirmed by the line 202 case. ✓
  • windowBuffer.Write ring logic is correct — both the len(p) >= maxWindowSize fast path and the overlapping copy(window, window[len-max:]) tail-shift are sound (Go copy is memmove-safe on overlap). ✓
  • Leading-whitespace position fix holdsisBatch only Peeks after the TeeReader is attached, so leading newlines reach the buffer (line 2 case). ✓
  • Build + go vet ./jsonrpc/ pass.

Nit (non-blocking — posted inline)

  • position undercounts for a single logical line longer than 512 B (pretty_error.go:63-68): when the marker's line began before the window was truncated, lineStart falls back to window start, so the printed position drifts low. Rare for JSON-RPC; caret stays aligned, only the number is off. A one-line comment noting the window bounds accuracy would do.

Carried-over (already discussed — fine to leave)

  • Per-element batch decode still emits raw err.Error() (server.go:401-406) instead of prettyParseError — deferring is reasonable since the sub-sliced RawMessage offsets are element-relative.
  • errorOffset's dropped lower-bound clamp and describeError's default branch remain unreachable today; the max(0, …) in prettyParseError already guards the slices, so re-adding the clamp only matters once the SONIC path feeds offsets through (per your TODO).

Note: go test ./jsonrpc/ needs approval in this environment, so I traced the changed/added table cases against the code by hand; go build + go vet pass and agree with every expected string.
granza/json-pretty-print

Comment thread jsonrpc/pretty_error.go
Comment on lines +63 to +68
func lineAndColumn(c *windowBuffer, markerPos int) (line, col int) {
line = c.newlinesSeen - bytes.Count(c.window[markerPos:], []byte{'\n'}) + 1
lineStart := bytes.LastIndexByte(c.window[:markerPos], '\n') + 1
col = utf8.RuneCount(c.window[lineStart:markerPos]) + 1
return line, col
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] position is undercounted when a single logical line is longer than the window (maxWindowSize = 512).

lineStart := bytes.LastIndexByte(c.window[:markerPos], '\n') + 1 falls back to 0 when the marker's line began before the window was truncated (no newline left in the window). The column is then measured from the window start rather than the true start of the line, so the reported position drifts low.

This only bites for a single JSON line >512 bytes with the error late in that line (rare for JSON-RPC, and the existing "long line is windowed" case at ~230 bytes still fits). The caret itself stays aligned because truncateAround works off the same windowed line — only the printed position number is off. Worth a one-line comment noting the window bounds position accuracy, since the whole feature is about accurate positions.

describeError/the slices are all safe here — markerPos is bounded to [0, len(window)] by max(0, absOffset-windowStart) in prettyParseError, so no OOB. Non-blocking.

Comment thread jsonrpc/pretty_error.go
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 8, 2026

Claude finished @RafaelGranza's task in 4m 38s —— View job


Test the windowBuffer large-write fast path (pretty_error.go:33)

Addressing @rodrodros's request to cover the len(p) >= maxWindowSize execution path.

  • Read pretty_error.go + server.go + tests
  • Identify the uncovered path (single WritemaxWindowSize)
  • Add a focused test exercising it
  • go vet ./jsonrpc/ passes (compiles the test; go test needs approval in this env — traced assertions by hand)
  • ⚠️ Push failedgithub-actions[bot] was denied write access (403)

Why a separate internal test file

windowBuffer is unexported and server_test.go is package jsonrpc_test (external), so it can't reach windowBuffer.Write directly. The end-to-end table only hits line 33 indirectly via bufio/json.Decoder chunking, which is why codecov flagged it. The new file jsonrpc/pretty_error_internal_test.go is package jsonrpc and pins the branch directly — including the append(c.window[:0], …) reset and the consumedBytes/newlinesSeen accounting.

⚠️ Couldn't push

I committed locally but git push returns Permission to NethermindEth/juno.git denied to github-actions[bot] (403). You'll need to add the file yourself — here it is:

jsonrpc/pretty_error_internal_test.go
package jsonrpc

import (
	"bytes"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// TestWindowBufferWrite covers the windowBuffer.Write fast path taken when a
// single write is at least maxWindowSize bytes (pretty_error.go). The end-to-end
// table in server_test.go only reaches it indirectly through bufio/json.Decoder
// chunking, so these cases pin the behaviour directly.
func TestWindowBufferWrite(t *testing.T) {
	t.Run("single write larger than the window keeps only the last bytes", func(t *testing.T) {
		var c windowBuffer
		input := bytes.Repeat([]byte("a\n"), maxWindowSize) // 2*maxWindowSize bytes, >= maxWindowSize

		n, err := c.Write(input)
		require.NoError(t, err)
		assert.Equal(t, len(input), n)

		require.Len(t, c.window, maxWindowSize)
		assert.Equal(t, input[len(input)-maxWindowSize:], c.window)
		assert.Equal(t, len(input), c.consumedBytes)
		assert.Equal(t, bytes.Count(input, []byte{'\n'}), c.newlinesSeen)
	})

	t.Run("write of exactly maxWindowSize takes the fast path", func(t *testing.T) {
		var c windowBuffer
		input := bytes.Repeat([]byte("x"), maxWindowSize)

		n, err := c.Write(input)
		require.NoError(t, err)
		assert.Equal(t, maxWindowSize, n)

		assert.Equal(t, input, c.window)
		assert.Equal(t, maxWindowSize, c.consumedBytes)
		assert.Equal(t, 0, c.newlinesSeen)
	})

	t.Run("large write after a small one discards earlier content", func(t *testing.T) {
		var c windowBuffer

		// Seed the window with a small write so the fast path's
		// append(c.window[:0], ...) has prior content to overwrite.
		_, err := c.Write([]byte("leading\n"))
		require.NoError(t, err)

		big := bytes.Repeat([]byte("b"), maxWindowSize+10)
		_, err = c.Write(big)
		require.NoError(t, err)

		require.Len(t, c.window, maxWindowSize)
		assert.Equal(t, big[len(big)-maxWindowSize:], c.window)
		assert.NotContains(t, string(c.window), "leading")
		assert.Equal(t, len("leading\n")+len(big), c.consumedBytes)
		assert.Equal(t, 1, c.newlinesSeen) // only the seed write carried a newline
	})
}

The three sub-cases cover: a ≥ maxWindowSize write keeping only the last bytes (line 32–33), the exact-boundary == case, and a large write after a small one to prove the window is reset rather than appended. If you'd prefer it folded into an existing file or named differently, let me know.

@RafaelGranza RafaelGranza force-pushed the granza/json-pretty-print branch from 6232524 to 8b8ab4a Compare June 8, 2026 13:34
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 8, 2026

Claude finished @RafaelGranza's task in 3m 14s —— View job


Re-review: 8b8ab4a33 — realistic JSON in parse errors

  • Read latest commits & changed files
  • Check whether the windowBuffer.Write fast path (pretty_error.go:31-33) is now tested
  • Re-verify offset / line / position + windowing math
  • go build + go vet ./jsonrpc/ ✓ (go test needs approval in this env — traced table cases by hand)

This round adds realistic multi-line Starknet table cases and clearer param-count messages. No blockers.

Verified

  • @rodrodros's two approving-review asks are both done: real multi-line Starknet requests with errors (starknet_getStorageAt, starknet_call, nested params objects), and columnposition everywhere. ✓
  • Param-error messages improved and accurate: missing non-optional param fieldmissing required params: num; missing/unexpected params in listexpected between 1 and 3 params, got 4. Tests updated to match. ✓
  • Windowing / offset / line / position math holds across the new cases — right-window (position 174), both-sides window (position 155), window-slide line number (line 42), maxContextRows-capped context (line 7), oversized-line context drop (line 5), and rune-counted column (👍position 8). Traced each against the code. ✓
  • No panic on untrusted input: markerPos = max(0, absOffset - windowStart) stays in [0, len(window)]; every slice is bounded. The windowBuffer.Write ring/fast-path is memmove-safe. ✓
  • Build + vet pass.

Note — the pretty_error.go:33 test request

@rodrodros asked to test the len(p) >= maxWindowSize fast path. The new large-body cases (error past the captured window ~2.7 KB, oversized line drops all preceding context ~520 B) now reach it indirectly through bufio/json.Decoder chunking, so codecov should clear — but the coverage depends on the bufferSize=128 / decoder minRead=512 sizing and isn't pinned by a direct test. Left an inline note recommending the previously-drafted package jsonrpc internal test for robustness. Non-blocking.

Carried-over (already discussed — fine to leave)

  • Per-element batch decode still emits raw err.Error() (server.go, handleBatchRequest) — element-relative offsets make a clean swap awkward; a // TODO would stop it reading as an oversight.
  • errorOffset's dropped lower-bound clamp and describeError's default branch are unreachable today (stdlib offsets ≥ 1; prettyParseError's max(0, …) guards the slices) — re-add the max(0, min(…)) clamp when the SONIC path feeds offsets through, per the TODO.
  • position undercounts for a single logical line > 512 B (pretty_error.go:65); caret stays aligned, only the printed number drifts. A one-line comment noting the window bound would close it.

Note: go test ./jsonrpc/ requires approval in this environment, so I traced the changed/added table cases against the code by hand; go build + go vet ./jsonrpc/ pass and agree with every expected string.

Nice work landing the realistic-request coverage @rodrodros asked for.
granza/json-pretty-print

Comment thread jsonrpc/pretty_error.go
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 8, 2026

Claude finished @RafaelGranza's task in 3m 24s —— View job


Re-review: 9399128be — oversized single-line test case

  • Read latest commit & changed files
  • Verify the new test's offset / line / position math
  • Check whether windowBuffer.Write fast path (pretty_error.go:31-33) is now directly tested
  • go build + go vet ./jsonrpc/ ✓ (go test needs approval in this env — traced by hand)

This commit adds one table case ("oversized single-line input keeps only the trailing window"). It's correct, and go build + go vet ./jsonrpc/ pass. No blockers.

Verified

  • The reported position 510 is the windowed position, not the true one — and that's the expected behavior here. Traced it: the @ sits at byte offset 765 (true position 766), but with the body ≈768 B and a 512 B window, windowStart = 768 − 512 = 256, so markerPos = 765 − 256 = 509. The line has no newline in the window, so lineStart = 0 and col = 509 + 1 = 510. The caret still lands correctly because truncateAround works off the same windowed line. ✓
  • So this case pins the known position-undercount-on-lines->512B behavior (pretty_error.go:65) as a regression test. Good — but since the number is deliberately "wrong" (510 vs. true 766), a one-line // note: position is window-relative for lines longer than maxWindowSize next to the case (or at lineAndColumn) would stop a future reader from "fixing" it. This is the same nit flagged inline at pretty_error.go:65/68 — now it's load-bearing in a test, so the comment matters a bit more.

Re @rodrodros's "test this execution path" (pretty_error.go:33) — still indirect only

This new ≈768 B single-line body reaches the len(p) >= maxWindowSize fast path only indirectly, through the same bufio(128)/json.Decoder(512 minRead) chunking as the existing large cases — so codecov should stay green, but no test pins the branch directly. Whether line 33 is hit still depends on upstream buffer sizing; if bufferSize or the decode path changes, the branch silently goes back to uncovered with no failing test.

The direct package jsonrpc internal test drafted earlier (here) pins the reset, the == boundary, and the consumedBytes/newlinesSeen accounting regardless of chunking — it just couldn't be pushed (403). Recommend landing it to close out the request robustly. Non-blocking. Add it →

Carried-over (already discussed — fine to leave)

  • Per-element batch decode still emits raw err.Error() (server.go, handleBatchRequest) — element-relative offsets make a clean swap awkward; a // TODO would stop it reading as an oversight.
  • errorOffset's dropped lower-bound clamp and describeError's default branch are unreachable today (stdlib offsets ≥ 1; prettyParseError's max(0, …) guards the slices) — re-add the max(0, min(…)) clamp when the SONIC path feeds offsets through, per the TODO.

Note: go test ./jsonrpc/ requires approval in this environment, so I traced the new case against the code by hand; go build + go vet ./jsonrpc/ pass and agree with the expected string.

Nice — this lands a regression test for the windowed-position edge. The only open item is the direct fast-path unit test @rodrodros asked for.
granza/json-pretty-print

@RafaelGranza RafaelGranza force-pushed the granza/json-pretty-print branch from 9399128 to ad165d0 Compare June 8, 2026 16:14
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 8, 2026

Claude finished @RafaelGranza's task in 2m 58s —— View job


Re-review: ad165d0c1test: Address requests (head of granza/json-pretty-print)

  • Read latest commits & changed files
  • Verify offset / line / position + windowing math
  • Check panics / EOF / batch / untrusted-input edge cases
  • Build + go vet ./jsonrpc/ ✓ (go test needs approval in this env — traced table cases by hand)

The head commit adds a single table case ("oversized single-line input keeps only the trailing window"). It's correct, and go build + go vet ./jsonrpc/ pass. No blockers — this is the human-friendly error UX the issue asked for, and it's well-tested.

Verified

  • New case math is internally consistent. The @ sits at stream offset ≈765; with consumedBytes ≈ 768 and a 512 B window, windowStart = 256, markerPos = 509, no newline in the window → lineStart = 0 → reported position 510. truncateAround runs off the windowed line (512 B), so end == len(window) yields a leading ... with no trailing ellipsis, caret at col 75 — exactly the expected string. ✓
  • This case pins the known window-relative position behavior (pretty_error.go:65) as a regression test: for a single logical line longer than maxWindowSize, the printed position is window-relative (510), not the true position (766). The caret stays aligned because it's computed off the same windowed line. Deliberately "wrong" number → worth a one-line // position is window-relative for lines longer than maxWindowSize next to the case or at lineAndColumn, so a future reader doesn't "fix" it. Non-blocking (carried over from prior rounds).
  • No panic on untrusted input. markerPos = max(0, absOffset − windowStart) ∈ [0, len(window)]; every slice (window[:markerPos], window[markerPos:], offendingLine, describeSyntaxError) stays in range, including markerPos == len(window) (EOF) and empty input. The windowBuffer.Write ring/fast-path is memmove-safe.
  • buildArguments message changes are soundmissing required params: <names> (via utils.Filter/utils.Map) and expected between %d and %d params, got %d are accurate and read well; helpers exist in utils/slices.go. ✓
  • isBatch leading-whitespace fix holdsPeek(n) only (no Discard), run after the TeeReader is attached, so leading newlines reach the buffer and the line number stays correct ("leading blank line keeps line number"line 2). ✓

Carried-over (already discussed — fine to leave)

  • Per-element batch decode still emits raw err.Error() (server.go, handleBatchRequest) — element-relative json.RawMessage offsets make a clean swap awkward; a // TODO would stop it reading as an oversight.
  • errorOffset's dropped lower-bound clamp and describeError's default branch are unreachable today (stdlib offsets ≥ 1; prettyParseError's max(0, …) guards the slices) — re-add the max(0, min(…)) clamp when the SONIC path feeds offsets through, per the TODO at pretty_error.go:58.
  • Direct windowBuffer.Write fast-path unit test (pretty_error.go:31-33) — @RafaelGranza noted he won't be addressing this; coverage is reached indirectly via bufio/json.Decoder chunking, so it depends on buffer sizing. Acknowledged, non-blocking.

Note: go test ./jsonrpc/ requires approval in this environment, so I traced the changed/added table cases against the code by hand; go build + go vet ./jsonrpc/ pass and agree with every expected string. CI's table tests should confirm.

Clean round — nothing new to flag.
granza/json-pretty-print

Comment thread jsonrpc/pretty_error.go
Comment thread jsonrpc/pretty_error.go
Comment thread jsonrpc/pretty_error.go Outdated
Comment thread jsonrpc/server_test.go Outdated
Comment thread jsonrpc/server_test.go Outdated
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 8, 2026

Claude finished @RafaelGranza's task in 3m 17s —— View job


Re-review: c48abb45b + a903f3456 — tidy windowBuffer.Write + split out parse-error tests

  • Read latest commits & changed files
  • Verify windowBuffer.Write shift-before-append refactor
  • Verify the split-out parse-error test function
  • go build + go vet ./jsonrpc/ ✓ (go test needs approval here — traced table cases by hand)

Both of @rodrodros's last asks are addressed, and the refactor is correct. One CI concern below.

Verified

  • windowBuffer.Write now shifts before appending (pretty_error.go:36-39), resolving the "copy then overwrite / regrow past the cap" concern. The math checks out: in the slow path len(p) < maxWindowSize, so overflow = len(window) + len(p) − maxWindowSize < len(window)window[overflow:] is always in range, and after the copy+append the window is exactly maxWindowSize. No transient growth beyond the cap. The len(p) >= maxWindowSize fast path is unchanged. ✓
  • describeError's switch was reordered to match errorOffset (syntax → type → EOF), per the inline ask. Behavior is unchanged since the cases are disjoint error types. ✓
  • Parse-error table split into its own TestHandleParseError in the new jsonrpc/pretty_error_test.goTestHandle in server_test.go is now back to ~one concern, and the moved cases are a clean 1:1 (verified the diff). ✓
  • Blank line between each req/res pair added in both test maps, per the readability ask. ✓
  • Build + go vet ./jsonrpc/ pass.

Important — diff-lint will fail on the new test file

  • .golangci_diff.yaml:22-24 — the lll (100-col) exemption only lists jsonrpc/server_test.go, but the long single-line res strings moved to jsonrpc/pretty_error_test.go. 31 lines in the new file exceed 100 chars, so the golangci_diff job will flag every one. Broaden the path to cover both files — posted inline with a suggestion. (path: jsonrpc/(server|pretty_error)_test\.go)

Carried-over (already discussed — fine to leave)

  • Per-element batch decode still emits raw err.Error() (server.go, handleBatchRequest) — element-relative offsets make a clean swap awkward; a // TODO would stop it reading as an oversight.
  • errorOffset's dropped max(0, …) clamp and describeError's default branch are unreachable today (stdlib offsets ≥ 1; prettyParseError's max(0, …) guards the slices) — re-add the clamp when the SONIC path feeds offsets through, per the TODO at pretty_error.go:57.
  • position is window-relative for a single logical line > maxWindowSize (caret stays aligned; only the printed number drifts) — pinned by the "oversized single-line input…" case; a one-line comment at lineAndColumn would close it.
  • Direct windowBuffer.Write fast-path unit test — @RafaelGranza noted he won't address it; reached indirectly via bufio/json.Decoder chunking. Acknowledged.

Note: go test ./jsonrpc/ requires approval in this environment, so I traced the changed/added table cases against the code by hand; go build + go vet ./jsonrpc/ pass and agree with every expected string.

The only actionable item this round is the lll exemption path — without it the lint job fails on the freshly-split test file.
granza/json-pretty-print

Comment thread .golangci_diff.yaml
Comment on lines +22 to +24
- linters:
- lll
path: jsonrpc/server_test\.go
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[important] The lll exemption no longer covers where the long lines live.

a903f3456 moved the parse-error table (with its very long single-line res strings) out of server_test.go and into the new jsonrpc/pretty_error_test.go. 31 lines in that new file exceed the 100-char lll limit, but this exemption still only lists jsonrpc/server_test.go — so the golangci_diff job will flag every one of them.

Broadening the path to both test files fixes it:

Suggested change
- linters:
- lll
path: jsonrpc/server_test\.go
- linters:
- lll
path: jsonrpc/(server|pretty_error)_test\.go

(Or jsonrpc/.*_test\.go if you'd rather not enumerate.)

@RafaelGranza RafaelGranza deployed to Development June 8, 2026 18:31 — with GitHub Actions Active
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants