Skip to main content

http_grpc_rs/
error.rs

1use core::fmt;
2
3use http::header::{HeaderMap, HeaderValue};
4use percent_encoding::{AsciiSet, NON_ALPHANUMERIC, utf8_percent_encode};
5
6use super::status::GrpcStatus;
7
8const GRPC_STATUS: http::header::HeaderName = http::header::HeaderName::from_static("grpc-status");
9const GRPC_MESSAGE: http::header::HeaderName = http::header::HeaderName::from_static("grpc-message");
10
11/// Per gRPC spec: percent-encode `grpc-message` using RFC 3986 unreserved characters.
12const GRPC_MESSAGE_ENCODE_SET: &AsciiSet = &NON_ALPHANUMERIC.remove(b'-').remove(b'_').remove(b'.').remove(b'~');
13
14/// gRPC protocol errors.
15#[derive(Debug)]
16pub enum ProtocolError {
17    /// The body ended before a complete gRPC frame could be read.
18    IncompleteFrame,
19    /// A single gRPC message exceeded the configured size limit.
20    MessageTooLarge { size: usize, limit: usize },
21    /// Protobuf decode failed.
22    Decode(prost::DecodeError),
23    /// Protobuf encode failed.
24    Encode(prost::EncodeError),
25    /// Compression flag set but no encoding was configured.
26    CompressedWithoutEncoding,
27    /// Compression/decompression error.
28    Compress(String),
29    /// Compression is not supported (feature not enabled).
30    CompressUnsupported,
31}
32
33impl fmt::Display for ProtocolError {
34    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35        match *self {
36            Self::IncompleteFrame => f.write_str("incomplete grpc frame"),
37            Self::MessageTooLarge { size, limit } => {
38                write!(f, "grpc message size {size} exceeds limit {limit}")
39            }
40            Self::Decode(ref e) => write!(f, "protobuf decode: {e}"),
41            Self::Encode(ref e) => write!(f, "protobuf encode: {e}"),
42            Self::CompressedWithoutEncoding => f.write_str("compressed flag set but no grpc-encoding configured"),
43            Self::Compress(ref e) => write!(f, "compression error: {e}"),
44            Self::CompressUnsupported => f.write_str("grpc compression not supported"),
45        }
46    }
47}
48
49impl core::error::Error for ProtocolError {}
50
51/// Error type for gRPC operations. Carries a status code and optional message,
52/// and can produce gRPC trailers for error responses.
53pub struct GrpcError {
54    pub status: GrpcStatus,
55    pub message: Option<String>,
56}
57
58impl GrpcError {
59    pub fn new(status: GrpcStatus, msg: impl Into<String>) -> Self {
60        Self {
61            status,
62            message: Some(msg.into()),
63        }
64    }
65
66    pub fn status(status: GrpcStatus) -> Self {
67        Self { status, message: None }
68    }
69
70    /// Produce gRPC trailers (`grpc-status` and optionally `grpc-message`) from this error.
71    pub fn trailers(&self) -> HeaderMap {
72        let mut map = HeaderMap::with_capacity(2);
73        map.insert(GRPC_STATUS, HeaderValue::from(self.status as u16));
74        if let Some(ref msg) = self.message {
75            let encoded = utf8_percent_encode(msg, GRPC_MESSAGE_ENCODE_SET).to_string();
76            if let Ok(v) = HeaderValue::from_str(&encoded) {
77                map.insert(GRPC_MESSAGE, v);
78            }
79        }
80        map
81    }
82}
83
84impl fmt::Debug for GrpcError {
85    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86        f.debug_struct("GrpcError")
87            .field("status", &self.status)
88            .field("message", &self.message)
89            .finish()
90    }
91}
92
93impl fmt::Display for GrpcError {
94    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95        write!(f, "gRPC error {:?}", self.status)?;
96        if let Some(ref msg) = self.message {
97            write!(f, ": {msg}")?;
98        }
99        Ok(())
100    }
101}
102
103impl core::error::Error for GrpcError {}