-
Notifications
You must be signed in to change notification settings - Fork 156
chore: add Jspecify NullCheck in grpc transport (Fixes #334) #342
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
39538ab
34641e2
e290d63
56bb407
a43c25d
eed09e7
af4f445
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,8 @@ | |
| import io.a2a.client.transport.spi.interceptors.auth.AuthInterceptor; | ||
| import io.a2a.common.A2AHeaders; | ||
| import io.a2a.grpc.A2AServiceGrpc; | ||
| import io.a2a.grpc.A2AServiceGrpc.A2AServiceBlockingV2Stub; | ||
| import io.a2a.grpc.A2AServiceGrpc.A2AServiceStub; | ||
| import io.a2a.grpc.CancelTaskRequest; | ||
| import io.a2a.grpc.CreateTaskPushNotificationConfigRequest; | ||
| import io.a2a.grpc.DeleteTaskPushNotificationConfigRequest; | ||
|
|
@@ -28,7 +30,8 @@ | |
| import io.a2a.grpc.SendMessageResponse; | ||
| import io.a2a.grpc.StreamResponse; | ||
| import io.a2a.grpc.TaskSubscriptionRequest; | ||
|
|
||
| import io.a2a.grpc.utils.ProtoUtils.FromProto; | ||
| import io.a2a.grpc.utils.ProtoUtils.ToProto; | ||
| import io.a2a.spec.A2AClientException; | ||
| import io.a2a.spec.AgentCard; | ||
| import io.a2a.spec.DeleteTaskPushNotificationConfigParams; | ||
|
|
@@ -49,6 +52,7 @@ | |
| import io.grpc.StatusRuntimeException; | ||
| import io.grpc.stub.MetadataUtils; | ||
| import io.grpc.stub.StreamObserver; | ||
| import org.jspecify.annotations.Nullable; | ||
|
|
||
| public class GrpcTransport implements ClientTransport { | ||
|
|
||
|
|
@@ -60,14 +64,14 @@ public class GrpcTransport implements ClientTransport { | |
| Metadata.ASCII_STRING_MARSHALLER); | ||
| private final A2AServiceBlockingV2Stub blockingStub; | ||
| private final A2AServiceStub asyncStub; | ||
| private final List<ClientCallInterceptor> interceptors; | ||
| private final @Nullable List<ClientCallInterceptor> interceptors; | ||
| private AgentCard agentCard; | ||
|
|
||
| public GrpcTransport(Channel channel, AgentCard agentCard) { | ||
| this(channel, agentCard, null); | ||
| } | ||
|
|
||
| public GrpcTransport(Channel channel, AgentCard agentCard, List<ClientCallInterceptor> interceptors) { | ||
| public GrpcTransport(Channel channel, AgentCard agentCard, @Nullable List<ClientCallInterceptor> interceptors) { | ||
| checkNotNullParam("channel", channel); | ||
| this.asyncStub = A2AServiceGrpc.newStub(channel); | ||
| this.blockingStub = A2AServiceGrpc.newBlockingV2Stub(channel); | ||
|
|
@@ -76,10 +80,10 @@ public GrpcTransport(Channel channel, AgentCard agentCard, List<ClientCallInterc | |
| } | ||
|
|
||
| @Override | ||
| public EventKind sendMessage(MessageSendParams request, ClientCallContext context) throws A2AClientException { | ||
| public EventKind sendMessage(MessageSendParams request, @Nullable ClientCallContext context) throws A2AClientException { | ||
| checkNotNullParam("request", request); | ||
|
|
||
| SendMessageRequest sendMessageRequest = createGrpcSendMessageRequest(request, context); | ||
| SendMessageRequest sendMessageRequest = createGrpcSendMessageRequest(request); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| PayloadAndHeaders payloadAndHeaders = applyInterceptors(io.a2a.spec.SendMessageRequest.METHOD, sendMessageRequest, | ||
| agentCard, context); | ||
|
|
||
|
|
@@ -100,10 +104,10 @@ public EventKind sendMessage(MessageSendParams request, ClientCallContext contex | |
|
|
||
| @Override | ||
| public void sendMessageStreaming(MessageSendParams request, Consumer<StreamingEventKind> eventConsumer, | ||
| Consumer<Throwable> errorConsumer, ClientCallContext context) throws A2AClientException { | ||
| Consumer<Throwable> errorConsumer, @Nullable ClientCallContext context) throws A2AClientException { | ||
| checkNotNullParam("request", request); | ||
| checkNotNullParam("eventConsumer", eventConsumer); | ||
| SendMessageRequest grpcRequest = createGrpcSendMessageRequest(request, context); | ||
| SendMessageRequest grpcRequest = createGrpcSendMessageRequest(request); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here, |
||
| PayloadAndHeaders payloadAndHeaders = applyInterceptors(SendStreamingMessageRequest.METHOD, | ||
| grpcRequest, agentCard, context); | ||
| StreamObserver<StreamResponse> streamObserver = new EventStreamObserver(eventConsumer, errorConsumer); | ||
|
|
@@ -117,7 +121,7 @@ public void sendMessageStreaming(MessageSendParams request, Consumer<StreamingEv | |
| } | ||
|
|
||
| @Override | ||
| public Task getTask(TaskQueryParams request, ClientCallContext context) throws A2AClientException { | ||
| public Task getTask(TaskQueryParams request, @Nullable ClientCallContext context) throws A2AClientException { | ||
| checkNotNullParam("request", request); | ||
|
|
||
| GetTaskRequest.Builder requestBuilder = GetTaskRequest.newBuilder(); | ||
|
|
@@ -138,7 +142,7 @@ public Task getTask(TaskQueryParams request, ClientCallContext context) throws A | |
| } | ||
|
|
||
| @Override | ||
| public Task cancelTask(TaskIdParams request, ClientCallContext context) throws A2AClientException { | ||
| public Task cancelTask(TaskIdParams request, @Nullable ClientCallContext context) throws A2AClientException { | ||
| checkNotNullParam("request", request); | ||
|
|
||
| CancelTaskRequest cancelTaskRequest = CancelTaskRequest.newBuilder() | ||
|
|
@@ -157,7 +161,7 @@ public Task cancelTask(TaskIdParams request, ClientCallContext context) throws A | |
|
|
||
| @Override | ||
| public TaskPushNotificationConfig setTaskPushNotificationConfiguration(TaskPushNotificationConfig request, | ||
| ClientCallContext context) throws A2AClientException { | ||
| @Nullable ClientCallContext context) throws A2AClientException { | ||
| checkNotNullParam("request", request); | ||
|
|
||
| String configId = request.pushNotificationConfig().id(); | ||
|
|
@@ -180,7 +184,7 @@ public TaskPushNotificationConfig setTaskPushNotificationConfiguration(TaskPushN | |
| @Override | ||
| public TaskPushNotificationConfig getTaskPushNotificationConfiguration( | ||
| GetTaskPushNotificationConfigParams request, | ||
| ClientCallContext context) throws A2AClientException { | ||
| @Nullable ClientCallContext context) throws A2AClientException { | ||
| checkNotNullParam("request", request); | ||
|
|
||
| GetTaskPushNotificationConfigRequest grpcRequest = GetTaskPushNotificationConfigRequest.newBuilder() | ||
|
|
@@ -200,7 +204,7 @@ public TaskPushNotificationConfig getTaskPushNotificationConfiguration( | |
| @Override | ||
| public List<TaskPushNotificationConfig> listTaskPushNotificationConfigurations( | ||
| ListTaskPushNotificationConfigParams request, | ||
| ClientCallContext context) throws A2AClientException { | ||
| @Nullable ClientCallContext context) throws A2AClientException { | ||
| checkNotNullParam("request", request); | ||
|
|
||
| ListTaskPushNotificationConfigRequest grpcRequest = ListTaskPushNotificationConfigRequest.newBuilder() | ||
|
|
@@ -221,7 +225,7 @@ public List<TaskPushNotificationConfig> listTaskPushNotificationConfigurations( | |
|
|
||
| @Override | ||
| public void deleteTaskPushNotificationConfigurations(DeleteTaskPushNotificationConfigParams request, | ||
| ClientCallContext context) throws A2AClientException { | ||
| @Nullable ClientCallContext context) throws A2AClientException { | ||
| checkNotNullParam("request", request); | ||
|
|
||
| DeleteTaskPushNotificationConfigRequest grpcRequest = DeleteTaskPushNotificationConfigRequest.newBuilder() | ||
|
|
@@ -240,7 +244,7 @@ public void deleteTaskPushNotificationConfigurations(DeleteTaskPushNotificationC | |
|
|
||
| @Override | ||
| public void resubscribe(TaskIdParams request, Consumer<StreamingEventKind> eventConsumer, | ||
| Consumer<Throwable> errorConsumer, ClientCallContext context) throws A2AClientException { | ||
| Consumer<Throwable> errorConsumer, @Nullable ClientCallContext context) throws A2AClientException { | ||
| checkNotNullParam("request", request); | ||
| checkNotNullParam("eventConsumer", eventConsumer); | ||
|
|
||
|
|
@@ -261,7 +265,7 @@ public void resubscribe(TaskIdParams request, Consumer<StreamingEventKind> event | |
| } | ||
|
|
||
| @Override | ||
| public AgentCard getAgentCard(ClientCallContext context) throws A2AClientException { | ||
| public AgentCard getAgentCard(@Nullable ClientCallContext context) throws A2AClientException { | ||
| // TODO: Determine how to handle retrieving the authenticated extended agent card | ||
| return agentCard; | ||
| } | ||
|
|
@@ -270,7 +274,7 @@ public AgentCard getAgentCard(ClientCallContext context) throws A2AClientExcepti | |
| public void close() { | ||
| } | ||
|
|
||
| private SendMessageRequest createGrpcSendMessageRequest(MessageSendParams messageSendParams, ClientCallContext context) { | ||
| private SendMessageRequest createGrpcSendMessageRequest(MessageSendParams messageSendParams) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We shouldn't remove context here. |
||
| SendMessageRequest.Builder builder = SendMessageRequest.newBuilder(); | ||
| builder.setRequest(ToProto.message(messageSendParams.message())); | ||
| if (messageSendParams.configuration() != null) { | ||
|
|
@@ -285,8 +289,11 @@ private SendMessageRequest createGrpcSendMessageRequest(MessageSendParams messag | |
| /** | ||
| * Creates gRPC metadata from ClientCallContext headers. | ||
| * Extracts headers like X-A2A-Extensions and sets them as gRPC metadata. | ||
| * @param context the client call context containing headers, may be null | ||
| * @param payloadAndHeaders the payload and headers wrapper, may be null | ||
| * @return the gRPC metadata | ||
| */ | ||
| private Metadata createGrpcMetadata(ClientCallContext context, PayloadAndHeaders payloadAndHeaders) { | ||
| private Metadata createGrpcMetadata(@Nullable ClientCallContext context, @Nullable PayloadAndHeaders payloadAndHeaders) { | ||
|
ehsavoie marked this conversation as resolved.
|
||
| Metadata metadata = new Metadata(); | ||
|
|
||
| if (context != null && context.getHeaders() != null) { | ||
|
|
@@ -328,7 +335,7 @@ private Metadata createGrpcMetadata(ClientCallContext context, PayloadAndHeaders | |
| * @param payloadAndHeaders the payloadAndHeaders after applying any interceptors | ||
| * @return blocking stub with metadata interceptor | ||
| */ | ||
| private A2AServiceBlockingV2Stub createBlockingStubWithMetadata(ClientCallContext context, | ||
| private A2AServiceBlockingV2Stub createBlockingStubWithMetadata(@Nullable ClientCallContext context, | ||
| PayloadAndHeaders payloadAndHeaders) { | ||
| Metadata metadata = createGrpcMetadata(context, payloadAndHeaders); | ||
| return blockingStub.withInterceptors(MetadataUtils.newAttachHeadersInterceptor(metadata)); | ||
|
|
@@ -341,7 +348,7 @@ private A2AServiceBlockingV2Stub createBlockingStubWithMetadata(ClientCallContex | |
| * @param payloadAndHeaders the payloadAndHeaders after applying any interceptors | ||
| * @return async stub with metadata interceptor | ||
| */ | ||
| private A2AServiceStub createAsyncStubWithMetadata(ClientCallContext context, | ||
| private A2AServiceStub createAsyncStubWithMetadata(@Nullable ClientCallContext context, | ||
| PayloadAndHeaders payloadAndHeaders) { | ||
| Metadata metadata = createGrpcMetadata(context, payloadAndHeaders); | ||
| return asyncStub.withInterceptors(MetadataUtils.newAttachHeadersInterceptor(metadata)); | ||
|
|
@@ -351,7 +358,7 @@ private String getTaskPushNotificationConfigName(GetTaskPushNotificationConfigPa | |
| return getTaskPushNotificationConfigName(params.id(), params.pushNotificationConfigId()); | ||
| } | ||
|
|
||
| private String getTaskPushNotificationConfigName(String taskId, String pushNotificationConfigId) { | ||
| private String getTaskPushNotificationConfigName(String taskId, @Nullable String pushNotificationConfigId) { | ||
| StringBuilder name = new StringBuilder(); | ||
| name.append("tasks/"); | ||
| name.append(taskId); | ||
|
|
@@ -366,7 +373,7 @@ private String getTaskPushNotificationConfigName(String taskId, String pushNotif | |
| } | ||
|
|
||
| private PayloadAndHeaders applyInterceptors(String methodName, Object payload, | ||
| AgentCard agentCard, ClientCallContext clientCallContext) { | ||
| AgentCard agentCard, @Nullable ClientCallContext clientCallContext) { | ||
| PayloadAndHeaders payloadAndHeaders = new PayloadAndHeaders(payload, | ||
| clientCallContext != null ? clientCallContext.getHeaders() : null); | ||
| if (interceptors != null && ! interceptors.isEmpty()) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| @NullMarked | ||
| package io.a2a.client.transport.grpc; | ||
|
|
||
| import org.jspecify.annotations.NullMarked; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For improved robustness and to align with the non-null contract of the
agentCardfield (as per@NullMarkedon the package), it's good practice to validate theagentCardparameter for nullity in this public constructor. This ensures that theGrpcTransportis always instantiated in a valid state.Consider adding
checkNotNullParam("agentCard", agentCard);at the start of the constructor body.