Skip to content

Commit 8f4dc25

Browse files
kabirehsavoie
authored andcommitted
Use MapStruct to convert between spec-grpc and spec classes.
The spec-grpc classes are ugly, and we can't let users use them. When using MapStruct an effort has been made to not use 'manual' conversion (i.e. a 'default' implementation of the Mapper interface methods). When automatic conversion happens, when the spec-grpc and spec classes differ a compile error will be raised. Which in turn helps us keep our manually generated spec classes in sync with the spec-grpc ones.
1 parent dcd8361 commit 8f4dc25

File tree

68 files changed

+2557
-1006
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+2557
-1006
lines changed

client/base/src/main/java/io/a2a/client/ClientBuilder.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,17 +92,17 @@ private ClientTransport buildClientTransport() throws A2AClientException {
9292
AgentInterface agentInterface = findBestClientTransport();
9393

9494
// Get the transport provider associated with the protocol
95-
ClientTransportProvider clientTransportProvider = transportProviderRegistry.get(agentInterface.transport());
95+
ClientTransportProvider clientTransportProvider = transportProviderRegistry.get(agentInterface.protocolBinding());
9696
if (clientTransportProvider == null) {
97-
throw new A2AClientException("No client available for " + agentInterface.transport());
97+
throw new A2AClientException("No client available for " + agentInterface.protocolBinding());
9898
}
9999
Class<? extends ClientTransport> transportProtocolClass = clientTransportProvider.getTransportProtocolClass();
100100

101101
// Retrieve the configuration associated with the preferred transport
102102
ClientTransportConfig<? extends ClientTransport> clientTransportConfig = clientTransports.get(transportProtocolClass);
103103

104104
if (clientTransportConfig == null) {
105-
throw new A2AClientException("Missing required TransportConfig for " + agentInterface.transport());
105+
throw new A2AClientException("Missing required TransportConfig for " + agentInterface.protocolBinding());
106106
}
107107

108108
return clientTransportProvider.create(clientTransportConfig, agentCard, agentInterface.url());
@@ -113,7 +113,7 @@ private Map<String, String> getServerPreferredTransports() {
113113
serverPreferredTransports.put(agentCard.preferredTransport(), agentCard.url());
114114
if (agentCard.additionalInterfaces() != null) {
115115
for (AgentInterface agentInterface : agentCard.additionalInterfaces()) {
116-
serverPreferredTransports.putIfAbsent(agentInterface.transport(), agentInterface.url());
116+
serverPreferredTransports.putIfAbsent(agentInterface.protocolBinding(), agentInterface.url());
117117
}
118118
}
119119
return serverPreferredTransports;

client/transport/grpc/src/main/java/io/a2a/client/transport/grpc/GrpcTransport.java

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -160,27 +160,29 @@ public Task cancelTask(TaskIdParams request, @Nullable ClientCallContext context
160160
public ListTasksResult listTasks(ListTasksParams request, @Nullable ClientCallContext context) throws A2AClientException {
161161
checkNotNullParam("request", request);
162162

163-
ListTasksRequest.Builder requestBuilder = ListTasksRequest.newBuilder();
163+
ListTasksRequest.Builder builder = ListTasksRequest.newBuilder();
164164
if (request.contextId() != null) {
165-
requestBuilder.setContextId(request.contextId());
165+
builder.setContextId(request.contextId());
166166
}
167167
if (request.status() != null) {
168-
requestBuilder.setStatus(ToProto.taskState(request.status()));
168+
builder.setStatus(ToProto.taskState(request.status()));
169169
}
170170
if (request.pageSize() != null) {
171-
requestBuilder.setPageSize(request.pageSize());
171+
builder.setPageSize(request.pageSize());
172172
}
173173
if (request.pageToken() != null) {
174-
requestBuilder.setPageToken(request.pageToken());
174+
builder.setPageToken(request.pageToken());
175175
}
176176
if (request.historyLength() != null) {
177-
requestBuilder.setHistoryLength(request.historyLength());
177+
builder.setHistoryLength(request.historyLength());
178178
}
179-
if (request.includeArtifacts() != null && request.includeArtifacts()) {
180-
requestBuilder.setIncludeArtifacts(true);
179+
if (request.lastUpdatedAfter() != null) {
180+
builder.setLastUpdatedAfter(request.lastUpdatedAfter().toEpochMilli());
181181
}
182-
183-
ListTasksRequest listTasksRequest = requestBuilder.build();
182+
if (request.includeArtifacts() != null) {
183+
builder.setIncludeArtifacts(request.includeArtifacts());
184+
}
185+
ListTasksRequest listTasksRequest = builder.build();
184186
PayloadAndHeaders payloadAndHeaders = applyInterceptors(io.a2a.spec.ListTasksRequest.METHOD, listTasksRequest,
185187
agentCard, context);
186188

@@ -317,15 +319,7 @@ public void close() {
317319
}
318320

319321
private SendMessageRequest createGrpcSendMessageRequest(MessageSendParams messageSendParams, @Nullable ClientCallContext context) {
320-
SendMessageRequest.Builder builder = SendMessageRequest.newBuilder();
321-
builder.setRequest(ToProto.message(messageSendParams.message()));
322-
if (messageSendParams.configuration() != null) {
323-
builder.setConfiguration(ToProto.messageSendConfiguration(messageSendParams.configuration()));
324-
}
325-
if (messageSendParams.metadata() != null) {
326-
builder.setMetadata(ToProto.struct(messageSendParams.metadata()));
327-
}
328-
return builder.build();
322+
return ToProto.sendMessageRequest(messageSendParams);
329323
}
330324

331325
/**

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
import io.a2a.spec.MessageSendParams;
5656
import io.a2a.spec.OpenIdConnectSecurityScheme;
5757
import io.a2a.spec.Part;
58-
import io.a2a.spec.PushNotificationAuthenticationInfo;
58+
import io.a2a.spec.AuthenticationInfo;
5959
import io.a2a.spec.PushNotificationConfig;
6060
import io.a2a.spec.SecurityScheme;
6161
import io.a2a.spec.Task;
@@ -315,7 +315,7 @@ public void testA2AClientGetTaskPushNotificationConfig() throws Exception {
315315
PushNotificationConfig pushNotificationConfig = taskPushNotificationConfig.pushNotificationConfig();
316316
assertNotNull(pushNotificationConfig);
317317
assertEquals("https://example.com/callback", pushNotificationConfig.url());
318-
PushNotificationAuthenticationInfo authenticationInfo = pushNotificationConfig.authentication();
318+
AuthenticationInfo authenticationInfo = pushNotificationConfig.authentication();
319319
assertTrue(authenticationInfo.schemes().size() == 1);
320320
assertEquals("jwt", authenticationInfo.schemes().get(0));
321321
}
@@ -340,13 +340,13 @@ public void testA2AClientSetTaskPushNotificationConfig() throws Exception {
340340
new TaskPushNotificationConfig("de38c76d-d54c-436c-8b9f-4c2703648d64",
341341
new PushNotificationConfig.Builder()
342342
.url("https://example.com/callback")
343-
.authenticationInfo(new PushNotificationAuthenticationInfo(Collections.singletonList("jwt"),
343+
.authenticationInfo(new AuthenticationInfo(Collections.singletonList("jwt"),
344344
null))
345345
.build()), null);
346346
PushNotificationConfig pushNotificationConfig = taskPushNotificationConfig.pushNotificationConfig();
347347
assertNotNull(pushNotificationConfig);
348348
assertEquals("https://example.com/callback", pushNotificationConfig.url());
349-
PushNotificationAuthenticationInfo authenticationInfo = pushNotificationConfig.authentication();
349+
AuthenticationInfo authenticationInfo = pushNotificationConfig.authentication();
350350
assertEquals(1, authenticationInfo.schemes().size());
351351
assertEquals("jwt", authenticationInfo.schemes().get(0));
352352
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ public class JsonMessages {
1414
"url": "https://georoute-agent.example.com/a2a/v1",
1515
"preferredTransport": "JSONRPC",
1616
"additionalInterfaces" : [
17-
{"url": "https://georoute-agent.example.com/a2a/v1", "transport": "JSONRPC"},
18-
{"url": "https://georoute-agent.example.com/a2a/grpc", "transport": "GRPC"},
19-
{"url": "https://georoute-agent.example.com/a2a/json", "transport": "HTTP+JSON"}
17+
{"url": "https://georoute-agent.example.com/a2a/v1", "protocolBinding": "JSONRPC"},
18+
{"url": "https://georoute-agent.example.com/a2a/grpc", "protocolBinding": "GRPC"},
19+
{"url": "https://georoute-agent.example.com/a2a/json", "protocolBinding": "HTTP+JSON"}
2020
],
2121
"provider": {
2222
"organization": "Example Geo Services Inc.",

client/transport/rest/src/test/java/io/a2a/client/transport/rest/RestTransportTest.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
import io.a2a.spec.MessageSendParams;
3939
import io.a2a.spec.Part;
4040
import io.a2a.spec.Part.Kind;
41-
import io.a2a.spec.PushNotificationAuthenticationInfo;
41+
import io.a2a.spec.AuthenticationInfo;
4242
import io.a2a.spec.PushNotificationConfig;
4343
import io.a2a.spec.StreamingEventKind;
4444
import io.a2a.spec.Task;
@@ -206,7 +206,7 @@ public void testGetTask() throws Exception {
206206
assertEquals(1, task.getArtifacts().size());
207207
Artifact artifact = task.getArtifacts().get(0);
208208
assertEquals("artifact-1", artifact.artifactId());
209-
assertEquals("", artifact.name());
209+
assertNull(artifact.name());
210210
assertEquals(false, artifact.parts().isEmpty());
211211
assertEquals(Kind.TEXT, artifact.parts().get(0).getKind());
212212
assertEquals("Why did the chicken cross the road? To get to the other side!", ((TextPart) artifact.parts().get(0)).getText());
@@ -304,13 +304,13 @@ public void testSetTaskPushNotificationConfiguration() throws Exception {
304304
new PushNotificationConfig.Builder()
305305
.url("https://example.com/callback")
306306
.authenticationInfo(
307-
new PushNotificationAuthenticationInfo(Collections.singletonList("jwt"), null))
307+
new AuthenticationInfo(Collections.singletonList("jwt"), null))
308308
.build());
309309
TaskPushNotificationConfig taskPushNotificationConfig = client.setTaskPushNotificationConfiguration(pushedConfig, null);
310310
PushNotificationConfig pushNotificationConfig = taskPushNotificationConfig.pushNotificationConfig();
311311
assertNotNull(pushNotificationConfig);
312312
assertEquals("https://example.com/callback", pushNotificationConfig.url());
313-
PushNotificationAuthenticationInfo authenticationInfo = pushNotificationConfig.authentication();
313+
AuthenticationInfo authenticationInfo = pushNotificationConfig.authentication();
314314
assertEquals(1, authenticationInfo.schemes().size());
315315
assertEquals("jwt", authenticationInfo.schemes().get(0));
316316
}
@@ -338,7 +338,7 @@ public void testGetTaskPushNotificationConfiguration() throws Exception {
338338
PushNotificationConfig pushNotificationConfig = taskPushNotificationConfig.pushNotificationConfig();
339339
assertNotNull(pushNotificationConfig);
340340
assertEquals("https://example.com/callback", pushNotificationConfig.url());
341-
PushNotificationAuthenticationInfo authenticationInfo = pushNotificationConfig.authentication();
341+
AuthenticationInfo authenticationInfo = pushNotificationConfig.authentication();
342342
assertTrue(authenticationInfo.schemes().size() == 1);
343343
assertEquals("jwt", authenticationInfo.schemes().get(0));
344344
}
@@ -367,7 +367,7 @@ public void testListTaskPushNotificationConfigurations() throws Exception {
367367
assertNotNull(pushNotificationConfig);
368368
assertEquals("https://example.com/callback", pushNotificationConfig.url());
369369
assertEquals("10", pushNotificationConfig.id());
370-
PushNotificationAuthenticationInfo authenticationInfo = pushNotificationConfig.authentication();
370+
AuthenticationInfo authenticationInfo = pushNotificationConfig.authentication();
371371
assertTrue(authenticationInfo.schemes().size() == 1);
372372
assertEquals("jwt", authenticationInfo.schemes().get(0));
373373
assertEquals("", authenticationInfo.credentials());

client/transport/spi/src/test/java/io/a2a/client/transport/spi/interceptors/auth/AuthInterceptorTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ public void testAPIKeySecurityScheme() {
7979
"session-id",
8080
APIKeySecurityScheme.API_KEY,
8181
"secret-api-key",
82-
new APIKeySecurityScheme("header", "x-api-key", "API Key authentication"),
82+
new APIKeySecurityScheme(APIKeySecurityScheme.Location.HEADER, "x-api-key", "API Key authentication"),
8383
"x-api-key",
8484
"secret-api-key"
8585
);
@@ -258,7 +258,7 @@ void testAvailableSecuritySchemeNotInAgentCardSecuritySchemes() {
258258
@Test
259259
void testNoCredentialAvailable() {
260260
String schemeName = "apikey";
261-
SecurityScheme securityScheme = new APIKeySecurityScheme("header", "X-API-Key", "API Key authentication");
261+
SecurityScheme securityScheme = new APIKeySecurityScheme(APIKeySecurityScheme.Location.HEADER, "X-API-Key", "API Key authentication");
262262
AgentCard agentCard = createAgentCard(schemeName, securityScheme);
263263

264264
Map<String, Object> requestPayload = Map.of("test", "payload");

http-client/src/test/java/io/a2a/client/http/JsonMessages.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ public class JsonMessages {
1414
"url": "https://georoute-agent.example.com/a2a/v1",
1515
"preferredTransport": "JSONRPC",
1616
"additionalInterfaces" : [
17-
{"url": "https://georoute-agent.example.com/a2a/v1", "transport": "JSONRPC"},
18-
{"url": "https://georoute-agent.example.com/a2a/grpc", "transport": "GRPC"},
19-
{"url": "https://georoute-agent.example.com/a2a/json", "transport": "HTTP+JSON"}
17+
{"url": "https://georoute-agent.example.com/a2a/v1", "protocolBinding": "JSONRPC"},
18+
{"url": "https://georoute-agent.example.com/a2a/grpc", "protocolBinding": "GRPC"},
19+
{"url": "https://georoute-agent.example.com/a2a/json", "protocolBinding": "HTTP+JSON"}
2020
],
2121
"provider": {
2222
"organization": "Example Geo Services Inc.",

pom.xml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@
5454
<jakarta.json-api.version>2.1.3</jakarta.json-api.version>
5555
<jakarta.ws.rs-api.version>3.1.0</jakarta.ws.rs-api.version>
5656
<junit.version>5.13.4</junit.version>
57+
<mapstruct.version>1.6.3</mapstruct.version>
58+
<mapstruct-spi-protobuf.version>1.52.0</mapstruct-spi-protobuf.version>
5759
<mockito-core.version>5.17.0</mockito-core.version>
5860
<mockserver.version>5.15.0</mockserver.version>
5961
<mutiny-zero.version>1.1.1</mutiny-zero.version>
@@ -223,6 +225,16 @@
223225
<artifactId>jakarta.inject-api</artifactId>
224226
<version>${jakarta.inject.jakarta.inject-api.version}</version>
225227
</dependency>
228+
<dependency>
229+
<groupId>org.mapstruct</groupId>
230+
<artifactId>mapstruct</artifactId>
231+
<version>${mapstruct.version}</version>
232+
</dependency>
233+
<dependency>
234+
<groupId>org.mapstruct</groupId>
235+
<artifactId>mapstruct-processor</artifactId>
236+
<version>${mapstruct.version}</version>
237+
</dependency>
226238
<dependency>
227239
<groupId>jakarta.json</groupId>
228240
<artifactId>jakarta.json-api</artifactId>
@@ -341,6 +353,16 @@
341353
<artifactId>nullaway</artifactId>
342354
<version>${nullaway.version}</version>
343355
</path>
356+
<path>
357+
<groupId>org.mapstruct</groupId>
358+
<artifactId>mapstruct-processor</artifactId>
359+
<version>${mapstruct.version}</version>
360+
</path>
361+
<path>
362+
<groupId>no.entur.mapstruct.spi</groupId>
363+
<artifactId>protobuf-spi-impl</artifactId>
364+
<version>${mapstruct-spi-protobuf.version}</version>
365+
</path>
344366
<!-- Other annotation processors go here.
345367
346368
If 'annotationProcessorPaths' is set, processors will no longer be

server-common/src/main/java/io/a2a/server/AgentCardValidator.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ static void validateTransportConfiguration(AgentCard agentCard, Set<String> avai
8888
// check that the primary URL matches the URL for the preferred transport
8989
if (agentCard.additionalInterfaces() != null) {
9090
agentCard.additionalInterfaces().stream()
91-
.filter(agentInterface -> agentInterface.transport().equals(agentCard.preferredTransport()))
91+
.filter(agentInterface -> agentInterface.protocolBinding().equals(agentCard.preferredTransport()))
9292
.findFirst()
9393
.ifPresent(preferredTransportAgentInterface -> {
9494
if (!preferredTransportAgentInterface.url().equals(agentCard.url())) {
@@ -120,8 +120,8 @@ private static Set<String> getAgentCardTransports(AgentCard agentCard) {
120120
// Add additional interface transports
121121
if (agentCard.additionalInterfaces() != null) {
122122
for (AgentInterface agentInterface : agentCard.additionalInterfaces()) {
123-
if (agentInterface.transport() != null) {
124-
transportStrings.add(agentInterface.transport());
123+
if (agentInterface.protocolBinding() != null) {
124+
transportStrings.add(agentInterface.protocolBinding());
125125
}
126126
}
127127
}

spec-grpc/pom.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@
5050
<groupId>com.google.api.grpc</groupId>
5151
<artifactId>proto-google-common-protos</artifactId>
5252
</dependency>
53+
<dependency>
54+
<groupId>org.mapstruct</groupId>
55+
<artifactId>mapstruct</artifactId>
56+
</dependency>
57+
5358

5459
<!-- Annotation dependency for generated code -->
5560
<dependency>
@@ -72,6 +77,11 @@
7277
<artifactId>junit-jupiter-engine</artifactId>
7378
<scope>test</scope>
7479
</dependency>
80+
<dependency>
81+
<groupId>com.google.protobuf</groupId>
82+
<artifactId>protobuf-java-util</artifactId>
83+
<scope>provided</scope>
84+
</dependency>
7585
</dependencies>
7686

7787
<build>

0 commit comments

Comments
 (0)