Search code examples
rustredistonic

How to handle external errors when using Tonic where I must use tonic::Status?


I am making a Tonic-based gRPC microservice that uses the Redis client. I can't figure out an example of implicitly converting a RedisError into a tonic::Status when an asynchronous error occurs.

async fn make_transaction(
    &self,
    request: Request<TransactionRequest>,
) -> Result<Response<TransactionResponse>, Status> {
    let mut con = self.client.get_async_connection().await?;
    con.set("my_key", 42).await?;
    ...
}

The connection from Redis client can fail as well as the set. I would rather not use .map_err() since that seems to break the async.

I was thinking I need to implement the trait From<Status> and From<RedisError> but not sure how to do it. This is my attempt, but it doesn't work since Tonic wants a tonic::Status, not a ApiError struct that I made:

pub struct ApiError {}

impl From<tonic::Status> for ApiError {
    fn from(err: Status) -> ApiError {
        ApiError {  }
    }
}

impl From<RedisError> for Status {
    fn from(err: redis::RedisError) -> ApiError {
        ApiError {  }
    }
}

Solution

  • I managed to get around this type of problem by using a custom type as a transition vessel for the original error (which is RedisError in your case), so something like the following might work for you as well:

    pub struct ApiError {}
    
    impl From<RedisError> for ApiError {
        fn from(err: RedisError) -> Self {
            Self { }
        }
    }
    
    impl From<ApiError> for Status {
        fn from(err: ApiError) -> Self {
            Self::internal("Failed talking to Redis")
        }
    }
    
    async fn set_key(client: ..., key: &str, value: i64) -> Result<(), ApiError> {
        let mut con = client.get_async_connection().await?;
        con.set(key, value).await?
        ...
    }
    
    async fn make_transaction(
        &self,
        request: Request<TransactionRequest>,
    ) -> Result<Response<TransactionResponse>, Status> {
        set_key(self.client, "my_key".into(), 42).await?
    }
    

    I am not sure if this is the best way, but seems to do the job.