Skip to content

Commit 1069cd8

Browse files
ehsavoieclaude
andcommitted
fix: Fix error code handling in A2AServerRoutes
- Add catch block for JSONRPCError to return correct error codes - Add catch block for JsonSyntaxException (Gson) to return -32700 parse error - Wrap InvalidParamsError in parseRequestBody to preserve request ID - Ensures -32700 for malformed JSON and -32602 for invalid params 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent ba9616f commit 1069cd8

File tree

2 files changed

+160
-55
lines changed

2 files changed

+160
-55
lines changed

reference/jsonrpc/src/main/java/io/a2a/server/apps/quarkus/A2AServerRoutes.java

Lines changed: 88 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import com.fasterxml.jackson.core.JsonParseException;
2323
import com.fasterxml.jackson.core.JsonProcessingException;
2424
import com.fasterxml.jackson.core.io.JsonEOFException;
25+
import com.google.gson.JsonSyntaxException;
2526
import io.a2a.common.A2AHeaders;
2627
import io.a2a.grpc.utils.JSONRPCUtils;
2728
import io.a2a.server.ServerCallContext;
@@ -31,10 +32,15 @@
3132
import io.a2a.server.util.async.Internal;
3233
import io.a2a.spec.AgentCard;
3334
import io.a2a.spec.CancelTaskRequest;
35+
import io.a2a.spec.CancelTaskResponse;
3436
import io.a2a.spec.DeleteTaskPushNotificationConfigRequest;
37+
import io.a2a.spec.DeleteTaskPushNotificationConfigResponse;
3538
import io.a2a.spec.GetAuthenticatedExtendedCardRequest;
39+
import io.a2a.spec.GetAuthenticatedExtendedCardResponse;
3640
import io.a2a.spec.GetTaskPushNotificationConfigRequest;
41+
import io.a2a.spec.GetTaskPushNotificationConfigResponse;
3742
import io.a2a.spec.GetTaskRequest;
43+
import io.a2a.spec.GetTaskResponse;
3844
import io.a2a.spec.IdJsonMappingException;
3945
import io.a2a.spec.InternalError;
4046
import io.a2a.spec.InvalidParamsError;
@@ -46,13 +52,18 @@
4652
import io.a2a.spec.JSONRPCRequest;
4753
import io.a2a.spec.JSONRPCResponse;
4854
import io.a2a.spec.ListTaskPushNotificationConfigRequest;
55+
import io.a2a.spec.ListTaskPushNotificationConfigResponse;
4956
import io.a2a.spec.ListTasksRequest;
57+
import io.a2a.spec.ListTasksResponse;
5058
import io.a2a.spec.MethodNotFoundError;
5159
import io.a2a.spec.MethodNotFoundJsonMappingException;
5260
import io.a2a.spec.NonStreamingJSONRPCRequest;
5361
import io.a2a.spec.SendMessageRequest;
62+
import io.a2a.spec.SendMessageResponse;
5463
import io.a2a.spec.SendStreamingMessageRequest;
64+
import io.a2a.spec.SendStreamingMessageResponse;
5565
import io.a2a.spec.SetTaskPushNotificationConfigRequest;
66+
import io.a2a.spec.SetTaskPushNotificationConfigResponse;
5667
import io.a2a.spec.SubscribeToTaskRequest;
5768
import io.a2a.spec.UnsupportedOperationError;
5869
import io.a2a.transport.jsonrpc.handler.JSONRPCHandler;
@@ -103,16 +114,20 @@ public void invokeJSONRPCHandler(@Body String body, RoutingContext rc) {
103114
streaming = true;
104115
streamingResponse = processStreamingRequest(request, context);
105116
}
117+
} catch (JSONRPCError e) {
118+
error = new JSONRPCErrorResponse(e);
106119
} catch (JsonProcessingException e) {
107120
error = handleError(e);
121+
} catch (JsonSyntaxException e) {
122+
error = new JSONRPCErrorResponse(new JSONParseError(e.getMessage()));
108123
} catch (Throwable t) {
109124
error = new JSONRPCErrorResponse(new InternalError(t.getMessage()));
110125
} finally {
111126
if (error != null) {
112127
rc.response()
113128
.setStatusCode(200)
114129
.putHeader(CONTENT_TYPE, APPLICATION_JSON)
115-
.end(Utils.toJsonString(error));
130+
.end(serializeResponse(error));
116131
} else if (streaming) {
117132
final Multi<? extends JSONRPCResponse<?>> finalStreamingResponse = streamingResponse;
118133
executor.execute(() -> {
@@ -124,7 +139,7 @@ public void invokeJSONRPCHandler(@Body String body, RoutingContext rc) {
124139
rc.response()
125140
.setStatusCode(200)
126141
.putHeader(CONTENT_TYPE, APPLICATION_JSON)
127-
.end(Utils.toJsonString(nonStreamingResponse));
142+
.end(serializeResponse(nonStreamingResponse));
128143
}
129144
}
130145
}
@@ -163,29 +178,35 @@ public AgentCard getAgentCard() {
163178
return jsonRpcHandler.getAgentCard();
164179
}
165180

166-
private JSONRPCResponse<?> processNonStreamingRequest(
167-
NonStreamingJSONRPCRequest<?> request, ServerCallContext context) {
181+
private JSONRPCResponse<?> processNonStreamingRequest(NonStreamingJSONRPCRequest<?> request, ServerCallContext context) {
168182
if (request instanceof GetTaskRequest req) {
169183
return jsonRpcHandler.onGetTask(req, context);
170-
} else if (request instanceof CancelTaskRequest req) {
184+
}
185+
if (request instanceof CancelTaskRequest req) {
171186
return jsonRpcHandler.onCancelTask(req, context);
172-
} else if (request instanceof ListTasksRequest req) {
187+
}
188+
if (request instanceof ListTasksRequest req) {
173189
return jsonRpcHandler.onListTasks(req, context);
174-
} else if (request instanceof SetTaskPushNotificationConfigRequest req) {
190+
}
191+
if (request instanceof SetTaskPushNotificationConfigRequest req) {
175192
return jsonRpcHandler.setPushNotificationConfig(req, context);
176-
} else if (request instanceof GetTaskPushNotificationConfigRequest req) {
193+
}
194+
if (request instanceof GetTaskPushNotificationConfigRequest req) {
177195
return jsonRpcHandler.getPushNotificationConfig(req, context);
178-
} else if (request instanceof SendMessageRequest req) {
196+
}
197+
if (request instanceof SendMessageRequest req) {
179198
return jsonRpcHandler.onMessageSend(req, context);
180-
} else if (request instanceof ListTaskPushNotificationConfigRequest req) {
199+
}
200+
if (request instanceof ListTaskPushNotificationConfigRequest req) {
181201
return jsonRpcHandler.listPushNotificationConfig(req, context);
182-
} else if (request instanceof DeleteTaskPushNotificationConfigRequest req) {
202+
}
203+
if (request instanceof DeleteTaskPushNotificationConfigRequest req) {
183204
return jsonRpcHandler.deletePushNotificationConfig(req, context);
184-
} else if (request instanceof GetAuthenticatedExtendedCardRequest req) {
205+
}
206+
if (request instanceof GetAuthenticatedExtendedCardRequest req) {
185207
return jsonRpcHandler.onGetAuthenticatedExtendedCardRequest(req, context);
186-
} else {
187-
return generateErrorResponse(request, new UnsupportedOperationError());
188208
}
209+
return generateErrorResponse(request, new UnsupportedOperationError());
189210
}
190211

191212
private Multi<? extends JSONRPCResponse<?>> processStreamingRequest(
@@ -249,6 +270,51 @@ public String getUsername() {
249270
}
250271
}
251272

273+
private static String serializeResponse(JSONRPCResponse<?> response) {
274+
// For error responses, use Jackson serialization (errors are standardized)
275+
if (response instanceof JSONRPCErrorResponse error) {
276+
return JSONRPCUtils.toJsonRPCErrorResponse(error.getId(), error.getError());
277+
}
278+
if(response.getError() != null) {
279+
System.out.println("------------------------------------------------------------");
280+
System.out.println("We have an error " + response.getError());
281+
System.out.println("------------------------------------------------------------");
282+
return JSONRPCUtils.toJsonRPCErrorResponse(response.getId(), response.getError());
283+
}
284+
285+
// Convert domain response to protobuf message and serialize
286+
com.google.protobuf.MessageOrBuilder protoMessage = convertToProto(response);
287+
return JSONRPCUtils.toJsonRPCResultResponse(response.getId(), protoMessage
288+
);
289+
}
290+
291+
private static com.google.protobuf.MessageOrBuilder convertToProto(JSONRPCResponse<?> response) {
292+
if (response instanceof GetTaskResponse r) {
293+
return io.a2a.grpc.utils.ProtoUtils.ToProto.task(r.getResult());
294+
} else if (response instanceof CancelTaskResponse r) {
295+
return io.a2a.grpc.utils.ProtoUtils.ToProto.task(r.getResult());
296+
} else if (response instanceof SendMessageResponse r) {
297+
return io.a2a.grpc.utils.ProtoUtils.ToProto.taskOrMessage(r.getResult());
298+
} else if (response instanceof ListTasksResponse r) {
299+
return io.a2a.grpc.utils.ProtoUtils.ToProto.listTasksResult(r.getResult());
300+
} else if (response instanceof SetTaskPushNotificationConfigResponse r) {
301+
return io.a2a.grpc.utils.ProtoUtils.ToProto.setTaskPushNotificationConfigResponse(r.getResult());
302+
} else if (response instanceof GetTaskPushNotificationConfigResponse r) {
303+
return io.a2a.grpc.utils.ProtoUtils.ToProto.getTaskPushNotificationConfigResponse(r.getResult());
304+
} else if (response instanceof ListTaskPushNotificationConfigResponse r) {
305+
return io.a2a.grpc.utils.ProtoUtils.ToProto.listTaskPushNotificationConfigResponse(r.getResult());
306+
} else if (response instanceof DeleteTaskPushNotificationConfigResponse) {
307+
// DeleteTaskPushNotificationConfig has no result body, just return empty message
308+
return com.google.protobuf.Empty.getDefaultInstance();
309+
} else if (response instanceof GetAuthenticatedExtendedCardResponse r) {
310+
return io.a2a.grpc.utils.ProtoUtils.ToProto.getAuthenticatedExtendedCardResponse(r.getResult());
311+
} else if (response instanceof SendStreamingMessageResponse r) {
312+
return io.a2a.grpc.utils.ProtoUtils.ToProto.taskOrMessageStream(r.getResult());
313+
} else {
314+
throw new IllegalArgumentException("Unknown response type: " + response.getClass().getName());
315+
}
316+
}
317+
252318
// Port of import io.quarkus.vertx.web.runtime.MultiSseSupport, which is considered internal API
253319
private static class MultiSseSupport {
254320

@@ -323,9 +389,15 @@ public Buffer apply(Object o) {
323389
ReactiveRoutes.ServerSentEvent<?> ev = (ReactiveRoutes.ServerSentEvent<?>) o;
324390
long id = ev.id() != -1 ? ev.id() : count.getAndIncrement();
325391
String e = ev.event() == null ? "" : "event: " + ev.event() + "\n";
326-
return Buffer.buffer(e + "data: " + Utils.toJsonString(ev.data()) + "\nid: " + id + "\n\n");
392+
String data = ev.data() instanceof JSONRPCResponse
393+
? serializeResponse((JSONRPCResponse<?>) ev.data())
394+
: Utils.toJsonString(ev.data());
395+
return Buffer.buffer(e + "data: " + data + "\nid: " + id + "\n\n");
327396
}
328-
return Buffer.buffer("data: " + Utils.toJsonString(o) + "\nid: " + count.getAndIncrement() + "\n\n");
397+
String data = o instanceof JSONRPCResponse
398+
? serializeResponse((JSONRPCResponse<?>) o)
399+
: Utils.toJsonString(o);
400+
return Buffer.buffer("data: " + data + "\nid: " + count.getAndIncrement() + "\n\n");
329401
}
330402
}), rc);
331403
}

spec-grpc/src/main/java/io/a2a/grpc/utils/JSONRPCUtils.java

Lines changed: 72 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import io.a2a.spec.IdJsonMappingException;
2727
import io.a2a.spec.InvalidAgentResponseError;
2828
import io.a2a.spec.InvalidParamsError;
29+
import io.a2a.spec.InvalidParamsJsonMappingException;
2930
import io.a2a.spec.InvalidRequestError;
3031
import io.a2a.spec.JSONParseError;
3132
import io.a2a.spec.JSONRPCError;
@@ -71,6 +72,14 @@ public static JSONRPCRequest<?> parseRequestBody(String body) throws JsonProcess
7172
String method = jsonRpc.get("method").getAsString();
7273
JsonElement paramsNode = jsonRpc.get("params");
7374

75+
try {
76+
return parseMethodRequest(version, id, method, paramsNode);
77+
} catch (InvalidParamsError e) {
78+
throw new InvalidParamsJsonMappingException(e.getMessage(), id);
79+
}
80+
}
81+
82+
private static JSONRPCRequest<?> parseMethodRequest(String version, Object id, String method, JsonElement paramsNode) throws InvalidParamsError, MethodNotFoundJsonMappingException {
7483
switch (method) {
7584
case GetTaskRequest.METHOD -> {
7685
io.a2a.grpc.GetTaskRequest.Builder builder = io.a2a.grpc.GetTaskRequest.newBuilder();
@@ -126,7 +135,7 @@ public static JSONRPCRequest<?> parseRequestBody(String body) throws JsonProcess
126135
return new SubscribeToTaskRequest(version, id, method, ProtoUtils.FromProto.taskIdParams(builder));
127136
}
128137
default ->
129-
throw new MethodNotFoundJsonMappingException("Invalid method", getIdIfPossible(jsonRpc));
138+
throw new MethodNotFoundJsonMappingException("Invalid method", id);
130139
}
131140
}
132141

@@ -241,33 +250,33 @@ private static JSONRPCError processError(JsonObject error) {
241250
String message = error.has("message") ? error.get("message").getAsString() : null;
242251
Integer code = error.has("code") ? error.get("code").getAsInt() : null;
243252
String data = error.has("data") ? error.get("data").toString() : null;
244-
if(code != null) {
245-
switch (code) {
246-
case JSONParseError.DEFAULT_CODE:
247-
return new JSONParseError(code, message, data);
248-
case InvalidRequestError.DEFAULT_CODE:
249-
return new InvalidRequestError(code, message, data);
250-
case MethodNotFoundError.DEFAULT_CODE:
251-
return new MethodNotFoundError(code, message, data);
252-
case InvalidParamsError.DEFAULT_CODE:
253-
return new InvalidParamsError(code, message, data);
254-
case io.a2a.spec.InternalError.DEFAULT_CODE:
255-
return new io.a2a.spec.InternalError(code, message, data);
256-
case PushNotificationNotSupportedError.DEFAULT_CODE:
257-
return new PushNotificationNotSupportedError(code, message, data);
258-
case UnsupportedOperationError.DEFAULT_CODE:
259-
return new UnsupportedOperationError(code, message, data);
260-
case ContentTypeNotSupportedError.DEFAULT_CODE:
261-
return new ContentTypeNotSupportedError(code, message, data);
262-
case InvalidAgentResponseError.DEFAULT_CODE:
263-
return new InvalidAgentResponseError(code, message, data);
264-
case TaskNotCancelableError.DEFAULT_CODE:
265-
return new TaskNotCancelableError(code, message, data);
266-
case TaskNotFoundError.DEFAULT_CODE:
267-
return new TaskNotFoundError(code, message, data);
268-
default:
269-
return new JSONRPCError(code, message, data);
270-
}
253+
if (code != null) {
254+
switch (code) {
255+
case JSONParseError.DEFAULT_CODE:
256+
return new JSONParseError(code, message, data);
257+
case InvalidRequestError.DEFAULT_CODE:
258+
return new InvalidRequestError(code, message, data);
259+
case MethodNotFoundError.DEFAULT_CODE:
260+
return new MethodNotFoundError(code, message, data);
261+
case InvalidParamsError.DEFAULT_CODE:
262+
return new InvalidParamsError(code, message, data);
263+
case io.a2a.spec.InternalError.DEFAULT_CODE:
264+
return new io.a2a.spec.InternalError(code, message, data);
265+
case PushNotificationNotSupportedError.DEFAULT_CODE:
266+
return new PushNotificationNotSupportedError(code, message, data);
267+
case UnsupportedOperationError.DEFAULT_CODE:
268+
return new UnsupportedOperationError(code, message, data);
269+
case ContentTypeNotSupportedError.DEFAULT_CODE:
270+
return new ContentTypeNotSupportedError(code, message, data);
271+
case InvalidAgentResponseError.DEFAULT_CODE:
272+
return new InvalidAgentResponseError(code, message, data);
273+
case TaskNotCancelableError.DEFAULT_CODE:
274+
return new TaskNotCancelableError(code, message, data);
275+
case TaskNotFoundError.DEFAULT_CODE:
276+
return new TaskNotFoundError(code, message, data);
277+
default:
278+
return new JSONRPCError(code, message, data);
279+
}
271280
}
272281
return new JSONRPCError(code, message, data);
273282
}
@@ -350,23 +359,47 @@ public static String toJsonRPCRequest(String requestId, String method, com.googl
350359
}
351360
}
352361

353-
public static String toJsonRPCResponse(String requestId, String method, com.google.protobuf.MessageOrBuilder builder) {
362+
public static String toJsonRPCResultResponse(Object requestId, com.google.protobuf.MessageOrBuilder builder) {
354363
try (StringWriter result = new StringWriter(); JsonWriter output = GSON.newJsonWriter(result)) {
355364
output.beginObject();
356365
output.name("jsonrpc").value("2.0");
357-
String id = requestId;
358-
if (requestId == null) {
359-
id = UUID.randomUUID().toString();
360-
}
361-
output.name("id").value(id);
362-
if (method != null) {
363-
output.name("method").value(method);
366+
if (requestId != null) {
367+
if (requestId instanceof String string) {
368+
output.name("id").value(string);
369+
} else if (requestId instanceof Number number) {
370+
output.name("id").value(number);
371+
}
364372
}
365373
String resultValue = JsonFormat.printer().print(builder);
366-
output.name("params").jsonValue(resultValue);
374+
output.name("result").jsonValue(resultValue);
367375
output.endObject();
368-
String jsonPayload = result.toString();
369-
return jsonPayload;
376+
return result.toString();
377+
} catch (IOException ex) {
378+
throw new RuntimeException(ex);
379+
}
380+
}
381+
382+
public static String toJsonRPCErrorResponse(Object requestId, JSONRPCError error) {
383+
try (StringWriter result = new StringWriter(); JsonWriter output = GSON.newJsonWriter(result)) {
384+
output.beginObject();
385+
output.name("jsonrpc").value("2.0");
386+
if (requestId != null) {
387+
if (requestId instanceof String string) {
388+
output.name("id").value(string);
389+
} else if (requestId instanceof Number number) {
390+
output.name("id").value(number);
391+
}
392+
}
393+
output.name("error");
394+
output.beginObject();
395+
output.name("code").value(error.getCode());
396+
output.name("message").value(error.getMessage());
397+
if (error.getData() != null) {
398+
output.name("data").value(error.getData().toString());
399+
}
400+
output.endObject();
401+
output.endObject();
402+
return result.toString();
370403
} catch (IOException ex) {
371404
throw new RuntimeException(ex);
372405
}

0 commit comments

Comments
 (0)