gRPC supports returning different messsage types based on runtime conditions through server-side logic and Protocol Buffers' type system. Two primary approaches enable this functionality:
1. Polymorphic Responses via oneof
The Protocol Buffers oneof feature allows defining a response message that may contain one of several predefined field types. This creates a type-safe union for conditional returns.
syntax = "proto3";
package storage;
message DataFetchResponse {
oneof content {
FileData file = 1;
AccessError error = 2;
}
}
message FileData {
bytes payload = 1;
string checksum = 2;
}
message AccessError {
int32 status_code = 1;
string reason = 2;
}
Server implementation (Go):
type StorageService struct {
pb.UnimplementedStorageServer
}
func (s *StorageService) FetchFile(ctx context.Context, req *pb.FileRequest) (*pb.DataFetchResponse, error) {
if req.AccessKey == "" {
return &pb.DataFetchResponse{
Content: &pb.DataFetchResponse_Error{
Error: &pb.AccessError{StatusCode: 403, Reason: "Invalid credentials"},
},
}, nil
}
return &pb.DataFetchResponse{
Content: &pb.DataFetchResponse_File{
File: &pb.FileData{Payload: []byte("file_content"), Checksum: "a1b2c3"},
},
}, nil
}
2. Distinct Response Messages
Services can define multiple response types and return them conditionally through separate RPC method signatures:
syntax = "proto3";
package auth;
service AuthService {
rpc ValidateToken(TokenRequest) returns (AuthResult);
}
message TokenRequest {
string token = 1;
}
message AuthResult {
oneof outcome {
ValidationSuccess success = 1;
ValidationFailure failure = 2;
}
}
message ValidationSuccess {
string user_id = 1;
int64 expires_at = 2;
}
message ValidationFailure {
int32 error_code = 1;
string description = 2;
}
Server implementation (Python):
class AuthServicer(auth_pb2_grpc.AuthServiceServicer):
def ValidateToken(self, request, context):
if len(request.token) < 10:
return auth_pb2.AuthResult(
outcome=auth_pb2.AuthResult(
failure=auth_pb2.ValidationFailure(
error_code=401,
description="Invalid token format"
)
)
)
return auth_pb2.AuthResult(
outcome=auth_pb2.AuthResult(
success=auth_pb2.ValidationSuccess(
user_id="usr_123",
expires_at=1710000000
)
)
)
Both methods leverage Protocol Buffers' type system to enable conditional response structures. The oneof approach provides explicit type constraints within a single message, while distinct message definitions offer structural separation for complex scenarios.