Skip to content

Commit 8d20f65

Browse files
committed
[#491] Encapsulate the payload of Push notifications
The payload of push notifications is encapsulated by the `kind` of payload as specified in [§ 4.3.3. Push Notification Payload](https://a2a-protocol.org/latest/specification/#433-push-notification-payload) This commit only works for `task` payload and will required more work to support additional payloads (tracked by #490). This fixes #491 Signed-off-by: Jeff Mesnil <[email protected]>
1 parent 686e40a commit 8d20f65

File tree

4 files changed

+46
-3
lines changed

4 files changed

+46
-3
lines changed

server-common/src/main/java/io/a2a/server/tasks/BasePushNotificationSender.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ private boolean dispatchNotification(Task task, PushNotificationConfig pushInfo)
8080

8181
String body;
8282
try {
83-
body = Utils.OBJECT_MAPPER.writeValueAsString(task);
83+
body = Utils.marshalFrom(task);
8484
} catch (JsonProcessingException e) {
8585
LOGGER.debug("Error writing value as string: {}", e.getMessage(), e);
8686
return false;

server-common/src/test/java/io/a2a/server/requesthandlers/AbstractA2ARequestHandlerTest.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import java.util.concurrent.Executors;
1414
import java.util.function.Consumer;
1515

16+
import com.fasterxml.jackson.core.JsonFactory;
17+
import com.fasterxml.jackson.core.JsonParser;
1618
import jakarta.enterprise.context.Dependent;
1719

1820
import io.a2a.client.http.A2AHttpClient;
@@ -199,7 +201,17 @@ public PostBuilder body(String body) {
199201

200202
@Override
201203
public A2AHttpResponse post() throws IOException, InterruptedException {
202-
tasks.add(Utils.OBJECT_MAPPER.readValue(body, Task.TYPE_REFERENCE));
204+
Task task;
205+
JsonFactory factory = new JsonFactory();
206+
try (JsonParser parser = factory.createParser(body)) {
207+
parser.nextToken(); // Should be START_OBJECT
208+
parser.nextToken(); // Move to first FIELD_NAME
209+
// FIXME when we expand the kind that can be pushed in #490
210+
String kind = parser.currentName();
211+
parser.nextToken(); // Move to VALUE token
212+
task = Utils.OBJECT_MAPPER.readValue(parser, Task.TYPE_REFERENCE);
213+
}
214+
tasks.add(task);
203215
try {
204216
return new A2AHttpResponse() {
205217
@Override

server-common/src/test/java/io/a2a/server/tasks/PushNotificationSenderTest.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616
import java.util.concurrent.TimeUnit;
1717
import java.util.function.Consumer;
1818

19+
import com.fasterxml.jackson.core.JsonFactory;
20+
import com.fasterxml.jackson.core.JsonParser;
21+
import com.fasterxml.jackson.databind.JsonNode;
22+
import com.fasterxml.jackson.databind.ObjectMapper;
1923
import org.junit.jupiter.api.BeforeEach;
2024
import org.junit.jupiter.api.Test;
2125

@@ -77,7 +81,16 @@ public A2AHttpResponse post() throws IOException, InterruptedException {
7781
}
7882

7983
try {
80-
Task task = Utils.OBJECT_MAPPER.readValue(body, Task.TYPE_REFERENCE);
84+
Task task;
85+
JsonFactory factory = new JsonFactory();
86+
try (JsonParser parser = factory.createParser(body)) {
87+
parser.nextToken(); // Should be START_OBJECT
88+
parser.nextToken(); // Move to first FIELD_NAME
89+
// FIXME when we expand the kind that can be pushed in #490
90+
String kind = parser.currentName();
91+
parser.nextToken(); // Move to VALUE token
92+
task = Utils.OBJECT_MAPPER.readValue(parser, Task.TYPE_REFERENCE);
93+
}
8194
tasks.add(task);
8295
urls.add(url);
8396
headers.add(new java.util.HashMap<>(requestHeaders));

spec/src/main/java/io/a2a/util/Utils.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,18 @@
22

33
import java.util.ArrayList;
44
import java.util.List;
5+
import java.util.Map;
56
import java.util.logging.Logger;
67

78
import com.fasterxml.jackson.core.JsonProcessingException;
89
import com.fasterxml.jackson.core.type.TypeReference;
910
import com.fasterxml.jackson.databind.ObjectMapper;
11+
import com.fasterxml.jackson.databind.node.ObjectNode;
1012
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
1113

1214
import io.a2a.spec.Artifact;
1315
import io.a2a.spec.Part;
16+
import io.a2a.spec.StreamingEventKind;
1417
import io.a2a.spec.Task;
1518
import io.a2a.spec.TaskArtifactUpdateEvent;
1619

@@ -69,6 +72,21 @@ public static <T> T unmarshalFrom(String data, TypeReference<T> typeRef) throws
6972
return OBJECT_MAPPER.readValue(data, typeRef);
7073
}
7174

75+
/**
76+
* Serializes a StreamingEventKind in a JSON string
77+
* <p>
78+
* The StreamingEventKind object is wrapped in a JSON field named from its kind (e.g. "task") before
79+
* it is serialized
80+
*
81+
* @param kind the StreamingEventKind to deserialize
82+
* @return a JSON String
83+
* @throws JsonProcessingException if JSON parsing fails
84+
*/
85+
public static String marshalFrom(StreamingEventKind kind) throws JsonProcessingException {
86+
Map<String, StreamingEventKind> wrapper = Map.of(kind.getKind(), kind);
87+
return OBJECT_MAPPER.writeValueAsString(wrapper);
88+
}
89+
7290
/**
7391
* Returns the provided value if non-null, otherwise returns the default value.
7492
* <p>

0 commit comments

Comments
 (0)