Skip to main content

GrpcHandler

Trait GrpcHandler 

Source
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:

RpcModeHandler MethodUse Case
Unarycall()Single request, single response
ServerStreamingcall_server_stream()Single request, multiple responses
ClientStreamingcall_client_stream()Multiple requests, single response
BidirectionalStreamingcall_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§

Source

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

Source

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§

Source

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.

Source

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

Source

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.

Source

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".

Implementors§