pub trait GrpcHandler: Send + Sync {
// Required methods
fn call(
&self,
request: GrpcRequestData,
) -> Pin<Box<dyn Future<Output = GrpcHandlerResult> + Send + '_>>;
fn service_name(&self) -> &str;
// Provided methods
fn rpc_mode(&self) -> RpcMode { ... }
fn call_server_stream(
&self,
request: GrpcRequestData,
) -> Pin<Box<dyn Future<Output = Result<Pin<Box<dyn Stream<Item = Result<Bytes, Status>> + Send>>, Status>> + Send + '_>> { ... }
fn call_client_stream(
&self,
request: StreamingRequest,
) -> Pin<Box<dyn Future<Output = Result<GrpcResponseData, Status>> + Send + '_>> { ... }
fn call_bidi_stream(
&self,
request: StreamingRequest,
) -> Pin<Box<dyn Future<Output = Result<Pin<Box<dyn Stream<Item = Result<Bytes, Status>> + Send>>, Status>> + Send + '_>> { ... }
}Expand description
Handler trait for gRPC requests
This is the language-agnostic interface that all gRPC handler implementations must satisfy. Language bindings (Python, TypeScript, Ruby, PHP) will implement this trait to bridge their runtime to Spikard’s gRPC server.
Handlers declare their RPC mode (unary vs streaming) via the rpc_mode() method.
The gRPC server uses this to route requests to either call() or call_server_stream().
§Examples
§Basic unary handler
use spikard_http::grpc::{GrpcHandler, RpcMode, GrpcRequestData, GrpcResponseData, GrpcHandlerResult};
use bytes::Bytes;
use std::pin::Pin;
use std::future::Future;
struct UnaryHandler;
impl GrpcHandler for UnaryHandler {
fn call(&self, request: GrpcRequestData) -> Pin<Box<dyn Future<Output = GrpcHandlerResult> + Send>> {
Box::pin(async move {
// Parse request.payload using protobuf deserialization
let user_id = extract_id_from_payload(&request.payload);
// Process business logic
let response_data = lookup_user(user_id).await?;
// Serialize response and return
Ok(GrpcResponseData {
payload: serialize_user(&response_data),
metadata: tonic::metadata::MetadataMap::new(),
})
})
}
fn service_name(&self) -> &str {
"users.UserService"
}
// Default rpc_mode() returns RpcMode::Unary
}§Server streaming handler
use spikard_http::grpc::{GrpcHandler, RpcMode, GrpcRequestData, MessageStream};
use bytes::Bytes;
use std::pin::Pin;
use std::future::Future;
struct StreamingHandler;
impl GrpcHandler for StreamingHandler {
fn call(&self, _request: GrpcRequestData) -> Pin<Box<dyn Future<Output = Result<GrpcResponseData, tonic::Status>> + Send>> {
// Unary call not used for streaming handlers, but must be implemented
Box::pin(async {
Err(tonic::Status::unimplemented("Use server streaming instead"))
})
}
fn service_name(&self) -> &str {
"events.EventService"
}
fn rpc_mode(&self) -> RpcMode {
RpcMode::ServerStreaming
}
fn call_server_stream(
&self,
request: GrpcRequestData,
) -> Pin<Box<dyn Future<Output = Result<MessageStream, tonic::Status>> + Send>> {
Box::pin(async move {
// Parse request to extract stream criteria (e.g., user_id)
let user_id = extract_id_from_payload(&request.payload);
// Generate messages (e.g., fetch events from database)
let events = fetch_user_events(user_id).await?;
let mut messages = Vec::new();
for event in events {
let serialized = serialize_event(&event);
messages.push(serialized);
}
// Convert to stream and return
Ok(Box::pin(futures_util::stream::iter(messages.into_iter().map(Ok))))
})
}
}§Dispatch Behavior
The gRPC server uses rpc_mode() to determine which handler method to call:
| RpcMode | Handler Method | Use Case |
|---|---|---|
Unary | call() | Single request, single response |
ServerStreaming | call_server_stream() | Single request, multiple responses |
ClientStreaming | call_client_stream() | Multiple requests, single response |
BidirectionalStreaming | call_bidi_stream() | Multiple requests, multiple responses |
§Error Handling
Both call() and call_server_stream() return gRPC error status values:
// Return a specific gRPC error
fn call(&self, request: GrpcRequestData) -> Pin<Box<dyn Future<Output = GrpcHandlerResult> + Send>> {
Box::pin(async {
let Some(id) = parse_id(&request.payload) else {
return Err(tonic::Status::invalid_argument("Missing user ID"));
};
// ... process ...
})
}Required Methods§
Sourcefn call(
&self,
request: GrpcRequestData,
) -> Pin<Box<dyn Future<Output = GrpcHandlerResult> + Send + '_>>
fn call( &self, request: GrpcRequestData, ) -> Pin<Box<dyn Future<Output = GrpcHandlerResult> + Send + '_>>
Handle a gRPC request
Takes the parsed request data and returns a future that resolves to either:
- Ok(GrpcResponseData): A successful response
- Err(tonic::Status): An error with appropriate gRPC status code
§Arguments
request- The parsed gRPC request containing service/method names, serialized payload, and metadata
§Returns
A future that resolves to a GrpcHandlerResult
Sourcefn service_name(&self) -> &str
fn service_name(&self) -> &str
Get the fully qualified service name this handler serves
This is used for routing requests to the appropriate handler. Should return the fully qualified service name as defined in the .proto file.
§Example
For a service defined as:
package mypackage;
service UserService { ... }This should return “mypackage.UserService”
Provided Methods§
Sourcefn rpc_mode(&self) -> RpcMode
fn rpc_mode(&self) -> RpcMode
Get the RPC mode this handler supports
Returns the type of RPC this handler implements. Used at handler registration to route requests to the appropriate handler method.
Default implementation returns RpcMode::Unary for backward compatibility.
Sourcefn call_server_stream(
&self,
request: GrpcRequestData,
) -> Pin<Box<dyn Future<Output = Result<Pin<Box<dyn Stream<Item = Result<Bytes, Status>> + Send>>, Status>> + Send + '_>>
fn call_server_stream( &self, request: GrpcRequestData, ) -> Pin<Box<dyn Future<Output = Result<Pin<Box<dyn Stream<Item = Result<Bytes, Status>> + Send>>, Status>> + Send + '_>>
Handle a server streaming RPC request
Takes a single request and returns a stream of response messages.
Default implementation adapts the unary call() response into a
single-message stream.
§Arguments
request- The parsed gRPC request
§Returns
A future that resolves to either a stream of messages or an error status
Sourcefn call_client_stream(
&self,
request: StreamingRequest,
) -> Pin<Box<dyn Future<Output = Result<GrpcResponseData, Status>> + Send + '_>>
fn call_client_stream( &self, request: StreamingRequest, ) -> Pin<Box<dyn Future<Output = Result<GrpcResponseData, Status>> + Send + '_>>
Handle a client streaming RPC call
Takes a stream of request messages and returns a single response message. Default implementation adapts to unary by requiring exactly one request message in the stream.
Sourcefn call_bidi_stream(
&self,
request: StreamingRequest,
) -> Pin<Box<dyn Future<Output = Result<Pin<Box<dyn Stream<Item = Result<Bytes, Status>> + Send>>, Status>> + Send + '_>>
fn call_bidi_stream( &self, request: StreamingRequest, ) -> Pin<Box<dyn Future<Output = Result<Pin<Box<dyn Stream<Item = Result<Bytes, Status>> + Send>>, Status>> + Send + '_>>
Handle a bidirectional streaming RPC call
Takes a stream of request messages and returns a stream of response messages. Default implementation adapts to unary by requiring exactly one request message and returning a single-message response stream.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".