Skip to content

Commit 4030494

Browse files
committed
fix: Updating error payloads after the transcoding changes correction.
* Following the changes from a2aproject/A2A#1627 * Updating HTTP error codes. * Ensuring JSONRPC uses data for error details. Signed-off-by: Emmanuel Hugonnet <ehugonne@redhat.com>
1 parent c12888d commit 4030494

10 files changed

Lines changed: 54 additions & 41 deletions

File tree

client/transport/jsonrpc/src/test/java/io/a2a/client/transport/jsonrpc/JsonMessages.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ public class JsonMessages {
216216
"error": {
217217
"code": -32702,
218218
"message": "Invalid parameters",
219-
"details": {"info": "Hello world"}
219+
"data": {"info": "Hello world"}
220220
}
221221
}""";
222222

client/transport/jsonrpc/src/test/java/io/a2a/client/transport/jsonrpc/JsonStreamingMessages.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ public class JsonStreamingMessages {
9999
"error": {
100100
"code": -32602,
101101
"message": "Invalid parameters",
102-
"details": {"info": "Missing required field"}
102+
"data": {"info": "Missing required field"}
103103
}
104104
}""";
105105

reference/rest/src/test/java/io/a2a/server/rest/quarkus/A2AServerRoutesTest.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -446,9 +446,9 @@ public void testDeleteTaskPushNotificationConfiguration_MethodNameSetInContext()
446446
public void testSendMessage_UnsupportedContentType_ReturnsContentTypeNotSupportedError() {
447447
// Arrange
448448
HTTPRestResponse mockErrorResponse = mock(HTTPRestResponse.class);
449-
when(mockErrorResponse.getStatusCode()).thenReturn(415);
449+
when(mockErrorResponse.getStatusCode()).thenReturn(400);
450450
when(mockErrorResponse.getContentType()).thenReturn(APPLICATION_JSON);
451-
when(mockErrorResponse.getBody()).thenReturn("{\"error\":{\"code\":415,\"status\":\"INVALID_ARGUMENT\",\"message\":\"Incompatible content types\",\"details\":[{\"reason\":\"CONTENT_TYPE_NOT_SUPPORTED\",\"domain\":\"a2a-protocol.org\"}]}}");
451+
when(mockErrorResponse.getBody()).thenReturn("{\"error\":{\"code\":400,\"status\":\"INVALID_ARGUMENT\",\"message\":\"Incompatible content types\",\"details\":[{\"reason\":\"CONTENT_TYPE_NOT_SUPPORTED\",\"domain\":\"a2a-protocol.org\"}]}}");
452452
when(mockRestHandler.createErrorResponse(any(ContentTypeNotSupportedError.class))).thenReturn(mockErrorResponse);
453453
when(mockRequest.getHeader(any(CharSequence.class))).thenReturn("text/plain");
454454

@@ -464,9 +464,9 @@ public void testSendMessage_UnsupportedContentType_ReturnsContentTypeNotSupporte
464464
public void testSendMessageStreaming_UnsupportedContentType_ReturnsContentTypeNotSupportedError() {
465465
// Arrange
466466
HTTPRestResponse mockErrorResponse = mock(HTTPRestResponse.class);
467-
when(mockErrorResponse.getStatusCode()).thenReturn(415);
467+
when(mockErrorResponse.getStatusCode()).thenReturn(400);
468468
when(mockErrorResponse.getContentType()).thenReturn(APPLICATION_JSON);
469-
when(mockErrorResponse.getBody()).thenReturn("{\"error\":{\"code\":415,\"status\":\"INVALID_ARGUMENT\",\"message\":\"Incompatible content types\",\"details\":[{\"reason\":\"CONTENT_TYPE_NOT_SUPPORTED\",\"domain\":\"a2a-protocol.org\"}]}}");
469+
when(mockErrorResponse.getBody()).thenReturn("{\"error\":{\"code\":400,\"status\":\"INVALID_ARGUMENT\",\"message\":\"Incompatible content types\",\"details\":[{\"reason\":\"CONTENT_TYPE_NOT_SUPPORTED\",\"domain\":\"a2a-protocol.org\"}]}}");
470470
when(mockRestHandler.createErrorResponse(any(ContentTypeNotSupportedError.class))).thenReturn(mockErrorResponse);
471471
when(mockRequest.getHeader(any(CharSequence.class))).thenReturn("text/plain");
472472

reference/rest/src/test/java/io/a2a/server/rest/quarkus/QuarkusA2ARestTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public void testSendMessageWithUnsupportedContentType() throws Exception {
4141
.header("Content-Type", "text/plain")
4242
.build();
4343
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
44-
Assertions.assertEquals(415, response.statusCode());
44+
Assertions.assertEquals(400, response.statusCode());
4545
Assertions.assertTrue(response.body().contains("CONTENT_TYPE_NOT_SUPPORTED"),
4646
"Expected CONTENT_TYPE_NOT_SUPPORTED in response body: " + response.body());
4747
}
@@ -58,7 +58,7 @@ public void testSendMessageWithUnsupportedProtocolVersion() throws Exception {
5858
.header("A2A-Version", "0.4.0")
5959
.build();
6060
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
61-
Assertions.assertEquals(400, response.statusCode());
61+
Assertions.assertEquals(501, response.statusCode());
6262
Assertions.assertTrue(response.body().contains("VERSION_NOT_SUPPORTED"),
6363
"Expected VERSION_NOT_SUPPORTED in response body: " + response.body());
6464
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -392,8 +392,8 @@ private static A2AError processError(JsonObject error) {
392392
String message = error.has("message") ? error.get("message").getAsString() : null;
393393
Integer code = error.has("code") ? error.get("code").getAsInt() : null;
394394
Map<String, Object> details = null;
395-
if (error.has("details") && error.get("details").isJsonObject()) {
396-
details =GSON.fromJson(error.get("details"), Map.class);
395+
if (error.has("data") && error.get("data").isJsonObject()) {
396+
details = GSON.fromJson(error.get("data"), Map.class);
397397
}
398398
if (code != null) {
399399
A2AErrorCodes errorCode = A2AErrorCodes.fromCode(code);
@@ -606,7 +606,7 @@ public static String toJsonRPCErrorResponse(Object requestId, A2AError error) {
606606
output.name("code").value(error.getCode());
607607
output.name("message").value(error.getMessage());
608608
if (!error.getDetails().isEmpty()) {
609-
output.name("details");
609+
output.name("data");
610610
GSON.toJson(error.getDetails(), Map.class, output);
611611
}
612612
output.endObject();

spec/src/main/java/io/a2a/spec/A2AErrorCodes.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,19 @@ public enum A2AErrorCodes {
2020
TASK_NOT_FOUND(-32001, "NOT_FOUND", 404),
2121

2222
/** Error code indicating the task cannot be canceled in its current state (-32002). */
23-
TASK_NOT_CANCELABLE(-32002, "FAILED_PRECONDITION", 409),
23+
TASK_NOT_CANCELABLE(-32002, "FAILED_PRECONDITION", 400),
2424

2525
/** Error code indicating push notifications are not supported by this agent (-32003). */
26-
PUSH_NOTIFICATION_NOT_SUPPORTED(-32003, "UNIMPLEMENTED", 400),
26+
PUSH_NOTIFICATION_NOT_SUPPORTED(-32003, "FAILED_PRECONDITION", 400),
2727

2828
/** Error code indicating the requested operation is not supported (-32004). */
29-
UNSUPPORTED_OPERATION(-32004, "UNIMPLEMENTED", 400),
29+
UNSUPPORTED_OPERATION(-32004, "UNIMPLEMENTED", 501),
3030

3131
/** Error code indicating the content type is not supported (-32005). */
32-
CONTENT_TYPE_NOT_SUPPORTED(-32005, "INVALID_ARGUMENT", 415),
32+
CONTENT_TYPE_NOT_SUPPORTED(-32005, "INVALID_ARGUMENT", 400),
3333

3434
/** Error code indicating the agent returned an invalid response (-32006). */
35-
INVALID_AGENT_RESPONSE(-32006, "INTERNAL", 502),
35+
INVALID_AGENT_RESPONSE(-32006, "INTERNAL", 500),
3636

3737
/** Error code indicating extended agent card is not configured (-32007). */
3838
EXTENDED_AGENT_CARD_NOT_CONFIGURED(-32007, "FAILED_PRECONDITION", 400),
@@ -43,7 +43,7 @@ public enum A2AErrorCodes {
4343

4444
/** Error code indicating the A2A protocol version specified in the request (via A2A-Version service parameter)
4545
* is not supported by the agent (-32009). */
46-
VERSION_NOT_SUPPORTED(-32009, "UNIMPLEMENTED", 400),
46+
VERSION_NOT_SUPPORTED(-32009, "UNIMPLEMENTED", 501),
4747

4848
/** JSON-RPC error code for invalid request structure (-32600). */
4949
INVALID_REQUEST(-32600, "INVALID_ARGUMENT", 400),

transport/grpc/src/main/java/io/a2a/transport/grpc/handler/GrpcHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -706,7 +706,7 @@ private <V> ServerCallContext createCallContext(StreamObserver<V> responseObserv
706706
* <li>{@link InternalError} → {@code INTERNAL}</li>
707707
* <li>{@link TaskNotFoundError} → {@code NOT_FOUND}</li>
708708
* <li>{@link TaskNotCancelableError} → {@code FAILED_PRECONDITION}</li>
709-
* <li>{@link PushNotificationNotSupportedError} → {@code UNIMPLEMENTED}</li>
709+
* <li>{@link PushNotificationNotSupportedError} → {@code FAILED_PRECONDITION}</li>
710710
* <li>{@link UnsupportedOperationError} → {@code UNIMPLEMENTED}</li>
711711
* <li>{@link JSONParseError} → {@code INTERNAL}</li>
712712
* <li>{@link ContentTypeNotSupportedError} → {@code INVALID_ARGUMENT}</li>

transport/grpc/src/test/java/io/a2a/transport/grpc/handler/GrpcHandlerTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ public void testPushNotificationsNotSupportedError() throws Exception {
251251
GrpcHandler handler = new TestGrpcHandler(card, requestHandler, internalExecutor);
252252
StreamRecorder<TaskPushNotificationConfig> streamRecorder = createTaskPushNotificationConfigRequest(handler,
253253
AbstractA2ARequestHandlerTest.MINIMAL_TASK.id(), AbstractA2ARequestHandlerTest.MINIMAL_TASK.id());
254-
assertGrpcError(streamRecorder, Status.Code.UNIMPLEMENTED);
254+
assertGrpcError(streamRecorder, Status.Code.FAILED_PRECONDITION);
255255
}
256256

257257
@Test
@@ -656,7 +656,7 @@ public void testListPushNotificationConfigNotSupported() throws Exception {
656656
.build();
657657
StreamRecorder<ListTaskPushNotificationConfigsResponse> streamRecorder = StreamRecorder.create();
658658
handler.listTaskPushNotificationConfigs(request, streamRecorder);
659-
assertGrpcError(streamRecorder, Status.Code.UNIMPLEMENTED);
659+
assertGrpcError(streamRecorder, Status.Code.FAILED_PRECONDITION);
660660
}
661661

662662
@Test
@@ -727,7 +727,7 @@ public void testDeletePushNotificationConfigNotSupported() throws Exception {
727727
.build();
728728
StreamRecorder<Empty> streamRecorder = StreamRecorder.create();
729729
handler.deleteTaskPushNotificationConfig(request, streamRecorder);
730-
assertGrpcError(streamRecorder, Status.Code.UNIMPLEMENTED);
730+
assertGrpcError(streamRecorder, Status.Code.FAILED_PRECONDITION);
731731
}
732732

733733
@Test

transport/rest/src/main/java/io/a2a/transport/rest/handler/RestHandler.java

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import static io.a2a.common.MediaType.APPLICATION_JSON;
44
import static io.a2a.server.util.async.AsyncUtils.createTubeConfig;
55

6-
import io.a2a.spec.A2AErrorCodes;
76

87
import java.time.Instant;
98
import java.time.format.DateTimeParseException;
@@ -40,6 +39,7 @@
4039
import io.a2a.server.version.A2AVersionValidator;
4140
import io.a2a.server.util.async.Internal;
4241
import io.a2a.spec.A2AError;
42+
import io.a2a.spec.A2AErrorCodes;
4343
import io.a2a.spec.AgentCard;
4444
import io.a2a.spec.CancelTaskParams;
4545
import io.a2a.spec.ContentTypeNotSupportedError;
@@ -122,13 +122,13 @@
122122
public class RestHandler {
123123

124124
private static final Logger log = Logger.getLogger(RestHandler.class.getName());
125-
private static final String TASK_STATE_PREFIX = "TASK_STATE_";
126125

127126
// Fields set by constructor injection cannot be final. We need a noargs constructor for
128127
// Jakarta compatibility, and it seems that making fields set by constructor injection
129128
// final, is not proxyable in all runtimes
130129
private AgentCard agentCard;
131-
private @Nullable Instance<AgentCard> extendedAgentCard;
130+
private @Nullable
131+
Instance<AgentCard> extendedAgentCard;
132132
private AgentCardCacheMetadata cacheMetadata;
133133
private RequestHandler requestHandler;
134134
private Executor executor;
@@ -377,7 +377,7 @@ public HTTPRestResponse createTaskPushNotificationConfiguration(ServerCallContex
377377
if (!taskIdFromBody.isEmpty() && !taskIdFromBody.equals(taskId)) {
378378
throw new InvalidParamsError("Task ID in request body (" + taskIdFromBody + ") does not match task ID in URL path (" + taskId + ").");
379379
}
380-
380+
381381
builder.setTenant(tenant);
382382
builder.setTaskId(taskId);
383383
TaskPushNotificationConfig result = requestHandler.onCreateTaskPushNotificationConfig(ProtoUtils.FromProto.createTaskPushNotificationConfig(builder), context);
@@ -766,29 +766,38 @@ private static int mapErrorToHttpStatus(A2AError error) {
766766
if (error instanceof InvalidParamsError) {
767767
return 422;
768768
}
769-
if (error instanceof MethodNotFoundError || error instanceof TaskNotFoundError) {
770-
return 404;
769+
if (error instanceof MethodNotFoundError) {
770+
return A2AErrorCodes.METHOD_NOT_FOUND.httpCode();
771+
}
772+
if (error instanceof TaskNotFoundError) {
773+
return A2AErrorCodes.TASK_NOT_FOUND.httpCode();
771774
}
772775
if (error instanceof TaskNotCancelableError) {
773-
return 409;
776+
return A2AErrorCodes.TASK_NOT_CANCELABLE.httpCode();
774777
}
775778
if (error instanceof UnsupportedOperationError) {
776-
return 501;
779+
return A2AErrorCodes.UNSUPPORTED_OPERATION.httpCode();
777780
}
778781
if (error instanceof ContentTypeNotSupportedError) {
779-
return 415;
782+
return A2AErrorCodes.CONTENT_TYPE_NOT_SUPPORTED.httpCode();
780783
}
781784
if (error instanceof InvalidAgentResponseError) {
782-
return 502;
785+
return A2AErrorCodes.INVALID_AGENT_RESPONSE.httpCode();
786+
}
787+
if (error instanceof ExtendedAgentCardNotConfiguredError) {
788+
return A2AErrorCodes.EXTENDED_AGENT_CARD_NOT_CONFIGURED.httpCode();
789+
}
790+
if (error instanceof ExtensionSupportRequiredError) {
791+
return A2AErrorCodes.EXTENSION_SUPPORT_REQUIRED.httpCode();
783792
}
784-
if (error instanceof ExtendedAgentCardNotConfiguredError
785-
|| error instanceof ExtensionSupportRequiredError
786-
|| error instanceof VersionNotSupportedError
787-
|| error instanceof PushNotificationNotSupportedError) {
788-
return 400;
793+
if (error instanceof VersionNotSupportedError) {
794+
return A2AErrorCodes.VERSION_NOT_SUPPORTED.httpCode();
795+
}
796+
if (error instanceof PushNotificationNotSupportedError) {
797+
return A2AErrorCodes.PUSH_NOTIFICATION_NOT_SUPPORTED.httpCode();
789798
}
790799
if (error instanceof InternalError) {
791-
return 500;
800+
return A2AErrorCodes.INTERNAL.httpCode();
792801
}
793802
return 500;
794803
}
@@ -827,7 +836,7 @@ public HTTPRestResponse getExtendedAgentCard(ServerCallContext context, String t
827836
} catch (A2AError e) {
828837
return createErrorResponse(e);
829838
} catch (Throwable t) {
830-
return createErrorResponse(500, new InternalError(t.getMessage()));
839+
return createErrorResponse(A2AErrorCodes.INTERNAL.httpCode(), new InternalError(t.getMessage()));
831840
}
832841
}
833842

@@ -1036,12 +1045,16 @@ public String toString() {
10361045
return "HTTPRestErrorResponse{error=" + error + '}';
10371046
}
10381047

1039-
private record ErrorBody(int code, String status, String message, List<ErrorDetail> details) {}
1048+
private record ErrorBody(int code, String status, String message, List<ErrorDetail> details) {
1049+
1050+
}
10401051

10411052
private record ErrorDetail(
10421053
@com.google.gson.annotations.SerializedName("@type") String type,
10431054
String reason,
10441055
String domain,
1045-
Map<String, Object> metadata) {}
1056+
Map<String, Object> metadata) {
1057+
1058+
}
10461059
}
10471060
}

transport/rest/src/test/java/io/a2a/transport/rest/handler/RestHandlerTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -798,7 +798,7 @@ public void testVersionNotSupportedErrorOnSendMessage() {
798798

799799
RestHandler.HTTPRestResponse response = handler.sendMessage(contextWithVersion, "", requestBody);
800800

801-
assertProblemDetail(response, 400,
801+
assertProblemDetail(response, 501,
802802
"VERSION_NOT_SUPPORTED",
803803
"Protocol version '2.0' is not supported. Supported versions: [1.0]");
804804
}

0 commit comments

Comments
 (0)