-
Couldn't load subscription status.
- Fork 34
Add support for gRPC error metadata propagation #194
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
base: main
Are you sure you want to change the base?
Conversation
…dling Grpc Client: - Updated InvalidStatusException to include a metadata field for error context. - GrpcClientResponseImpl now sets the metadata field when the returned status is not GrpcStatus.OK. gRPC Proto Plugin: - Populates the metadata field on non-OK responses, enabling clients to access additional error details. Vert.x gRPC-IO Client: - Parses metadata from response headers when the response is trailersOnly. (Previously, metadata was only parsed from trailers.) Note: - trailersOnly detection works but remains somewhat hacky; it could be improved by explicitly tracking whether any payload has been read.
Vert.x gRPC Server: - Introduced the GrpcErrorInfoProvider interface to allow server-side exceptions to expose gRPC error details, including custom metadata. - Made StatusException implement GrpcErrorInfoProvider, enabling richer error context. - Updated GrpcServerResponseImpl to detect exceptions implementing GrpcErrorInfoProvider and include their metadata in the gRPC error response sent to the client. Vert.x gRPC-IO Server: - Modified server logic to attach received metadata to the generated StatusRuntimeException, preserving trailer information in error propagation.
vertx-grpc-client-test: - Added tests to verify that error responses include and correctly expose metadata. vertx-grpc-server-test: - Added tests to ensure that server-side exceptions with metadata are correctly sent to clients. vertx-grpc-it-test: - Implemented integration tests covering end-to-end error propagation, including custom metadata in gRPC error responses.
| * </p> | ||
| */ | ||
|
|
||
| public interface GrpcErrorInfoProvider { |
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.
I don't think this interface is needed for implementing the feature ? if we can just use directly StatusException without it, then we should do it
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.
You're right it's not necessary, but there’s an important reason behind it:
StatusException is final, applications cannot extend it to include additional behavior or metadata relevant to their domain logic. As a result, if we rely solely on StatusException, applications are forced to throw a specific Vert.x class from within their business logic, making the error-handling tightly coupled to the transport layer.
The goal of introducing the interface is to decouple the business logic from the transport mechanism. This way, applications can throw their own domain-specific exceptions (e.g. InvalidUserInputException, PaymentRejectedException, etc.) and simply implement the interface to expose gRPC-compatible error data (status, message, metadata).
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.
I personally like the idea of users having the ability to write their own exceptions which are decoupled from the transport layer.
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.
Currently there are not tests using GrpcErrorInfoProvider, can you explain how this would be used in practice, it is not yet still clear to me how it can be used
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.
Thanks for the review. Let me give a concrete example to clarify the motivation for the interface.
Imagine a typical application structure where we define a base exception for our domain logic, and specific exceptions extend from it. These exceptions should not extend from StatusException, which is Vert.x/gRPC specific and has nothing to do with our application's business concerns (Furthermore, StatusException is final and we force the application to use it without chance of any subclassing).
Example
// Our base exception for the application
public abstract class AppException extends RuntimeException {
public AppException(String message) {
super(message);
}
}Then we define a business-specific exception, and we want it to be mappable to a gRPC status, without coupling it to Vert.x types:
// A business logic exception that we want to map to gRPC
public class UserNotFoundException extends AppException implements GrpcStatusException {
public UserNotFoundException(String userId) {
super("User not found: " + userId);
}
@Override
public Status getGrpcStatus() {
return Status.NOT_FOUND.withDescription(getMessage());
}
// add metadata
// add needed logic
}Note that:
UserNotFoundExceptionis part of our domain, and extendsAppException.- It implements
GrpcStatusExceptionto signal to the framework that it knows how to convert itself to a gRPC Status.
This keeps our application cleanly decoupled, while still enabling powerful mapping logic on the framework side.
Without this interface, we are forced to use StatusException directly within our application code.
StatusExceptionis a final class, meaning we cannot extend it to add application-specific data or behavior that might be relevant (e.g. error codes, metadata, logging context, etc.).- It couples our domain logic to Vert.x internals, which breaks separation of concerns.
This pattern is especially useful in microservice-to-microservice calls, where domain exceptions need to be translated to appropriate gRPC statuses but we still want to keep a clean architecture on the application side.
Hope this clarifies the intent!
|
@adparts we would like actually to use an exception mapper strategy to transform an exception into a StatusException, so we make it less intrusive, this mapper would be set for now on the ServiceBuilder as a |
This PR introduces support for propagating structured gRPC error metadata from the server to the client across the Vert.x gRPC stack. It allows both client and server components to handle and transmit metadata in error responses consistently.
Motivation:
Handling rich error information in gRPC is critical for debugging, client behavior, and user feedback. Until now, only status codes and messages were propagated, with no support for custom trailers or metadata.
This change allows:
This feature is required for internal use at our company, where we rely on Vert.x-based gRPC services and need to provide detailed error context (status, message, and metadata) from server to client. Without this functionality, we are unable to fully adopt vertx-grpc in our projects.
Changes
Server
Created GrpcErrorInfoProvider interface to allow exceptions to expose:
Made existing StatusException implement GrpcErrorInfoProvider and adapt GrpcServerResponseImpl to return the metadata from GrpcErrorInfoProvider
Client
Updated InvalidStatusException to include a metadata field.
Modified GrpcClientResponseImpl and the gRPC proto plugin to populate the metadata field when the returned status is not GrpcStatus.OK.
Vert.x gRPC-IO Layer
Server: Inject metadata into StatusRuntimeException for correct transmission to clients.
Client: Parse metadata from headers if the response is trailersOnly, not just from trailers.
Improved (but still not ideal) detection of trailersOnly responses by checking if a payload has been read.