fix(jsonrpc): lenient decode of error.data to preserve server errors - #386
fix(jsonrpc): lenient decode of error.data to preserve server errors#386dongjiang1989 wants to merge 1 commit into
Conversation
Change Error.Data from []*errordetails.Typed to json.RawMessage so that v0.3 servers returning error.data as a plain JSON object (or any non- array value) no longer cause the whole response decode to fail. The client now always preserves code+message and only converts to typed details when the data actually parses as such. Fixes a2aproject#385. Signed-off-by: dongjiang <dongjiang1989@126.com>
|
Verified the root cause and the fix against current main. On main, Error.Data is typed One correctness gap in the single-object branch. It uses errordetails.NewFromStruct(singleDetail), and NewFromStruct is NewTyped(StructType, details), so it hard-codes TypeURL to StructType and leaves any @type key inside Value. But Typed has a custom UnmarshalJSON (errordetails.go:67) that reads @type, sets TypeURL to it, and deletes it from Value. So a server that returns a single, non-array typed detail as an object, for example {"@type":"type.googleapis.com/google.rpc.ErrorInfo","reason":"...","metadata":{...}}, is mis-decoded: the ErrorInfo type is not recognized, the ErrorInfo metadata-extraction branch in FromJSONRPCError (the Suggested fix: route the object branch through *errordetails.Typed rather than NewFromStruct, so the same @type handling as the array path applies. Something like unmarshalling e.Data into a I confirmed this against your branch locally: a test that feeds a bare Narrow but real, and CI is green otherwise. |
Problem
The a2av0 compat JSON-RPC transport (and the v2 JSON-RPC client, which shares the same code path) decodes responses into
jsonrpc.ClientResponse, whoseError.Datafield is typed[]*errordetails.Typed(internal/jsonrpc/jsonrpc.go). JSON-RPC 2.0 / A2A v0.3 defineerror.dataas any JSON value, and v0.3 servers commonly return a plain object there (e.g.{"reason":"detail"}). When they do,json.Decode(&resp)fails withcannot unmarshal object into Go struct field ..., so the caller getsfailed to decode responseinstead of the server's actual error — the real code and message are lost.Repro: point an a2av0 JSON-RPC client at a v0.3 server that returns:
{"jsonrpc":"2.0","id":"1","error":{"code":-32603,"message":"real error message","data":{"reason":"detail"}}}The client surfaces
failed to decode response; the real message never reaches the caller.Same bug class as #318 (REST streaming error events), applied to the JSON-RPC path.
Closes #385.
Fix
Change
Error.Datafrom[]*errordetails.Typedtojson.RawMessageso the initial decode always succeeds, regardless of what shapeerror.datahas.FromJSONRPCErrorthen parses it leniently:[]*errordetails.Typed(the standard typed array).code+messagepreserved.ToJSONRPCErroris updated to marshal the typed slice intojson.RawMessageon the way out. The ordering ofTypedDetails(ErrorInfo first, then struct details) is preserved to keep existing round-trip behaviour.Because the fix lives in the shared
internal/jsonrpc/jsonrpc.go, both the v2 client (a2aclient/jsonrpc.go) and the v0 compat client (a2acompat/a2av0/jsonrpc_client.go) benefit.Tests
internal/jsonrpc/jsonrpc_test.go: addedTestFromJSONRPCError_NonTypedDatawith 5 sub-cases — plain object, string, typed array, null, absent — verifying code/message are always preserved and object data is surfaced as a struct detail.a2aclient/jsonrpc_test.go: addedTestJSONRPCTransport_ErrorWithNonTypedData(end-to-end transport test with a server returning a plain-objectdata) andTestJSONRPCTransport_ErrorWithStringData(stringdata). Updated the existingTestJSONRPCTransport_ErrorDetailsto marshal typed details intojson.RawMessageto match the new field type.Verification
go vet ./...— cleangolangci-lint run ./...— 0 issuesgo test -count=1 ./...— all 29 packages pass