Skip to main content

ringline_grpc/
error.rs

1/// gRPC status codes (<https://grpc.github.io/grpc/core/md_doc_statuscodes.html>).
2///
3/// Marked `#[non_exhaustive]` because the gRPC status code list is
4/// occasionally extended (the IANA registry currently stops at 16, but
5/// new codes could be added). Downstream `match` blocks must include a
6/// wildcard arm.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8#[repr(u8)]
9#[non_exhaustive]
10pub enum GrpcStatus {
11    Ok = 0,
12    Cancelled = 1,
13    Unknown = 2,
14    InvalidArgument = 3,
15    DeadlineExceeded = 4,
16    NotFound = 5,
17    AlreadyExists = 6,
18    PermissionDenied = 7,
19    ResourceExhausted = 8,
20    FailedPrecondition = 9,
21    Aborted = 10,
22    OutOfRange = 11,
23    Unimplemented = 12,
24    Internal = 13,
25    Unavailable = 14,
26    DataLoss = 15,
27    Unauthenticated = 16,
28}
29
30impl GrpcStatus {
31    /// Parse a status code from an integer value.
32    pub fn from_u8(v: u8) -> Self {
33        match v {
34            0 => Self::Ok,
35            1 => Self::Cancelled,
36            2 => Self::Unknown,
37            3 => Self::InvalidArgument,
38            4 => Self::DeadlineExceeded,
39            5 => Self::NotFound,
40            6 => Self::AlreadyExists,
41            7 => Self::PermissionDenied,
42            8 => Self::ResourceExhausted,
43            9 => Self::FailedPrecondition,
44            10 => Self::Aborted,
45            11 => Self::OutOfRange,
46            12 => Self::Unimplemented,
47            13 => Self::Internal,
48            14 => Self::Unavailable,
49            15 => Self::DataLoss,
50            16 => Self::Unauthenticated,
51            _ => Self::Unknown,
52        }
53    }
54}
55
56impl std::fmt::Display for GrpcStatus {
57    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58        match self {
59            Self::Ok => write!(f, "OK"),
60            Self::Cancelled => write!(f, "CANCELLED"),
61            Self::Unknown => write!(f, "UNKNOWN"),
62            Self::InvalidArgument => write!(f, "INVALID_ARGUMENT"),
63            Self::DeadlineExceeded => write!(f, "DEADLINE_EXCEEDED"),
64            Self::NotFound => write!(f, "NOT_FOUND"),
65            Self::AlreadyExists => write!(f, "ALREADY_EXISTS"),
66            Self::PermissionDenied => write!(f, "PERMISSION_DENIED"),
67            Self::ResourceExhausted => write!(f, "RESOURCE_EXHAUSTED"),
68            Self::FailedPrecondition => write!(f, "FAILED_PRECONDITION"),
69            Self::Aborted => write!(f, "ABORTED"),
70            Self::OutOfRange => write!(f, "OUT_OF_RANGE"),
71            Self::Unimplemented => write!(f, "UNIMPLEMENTED"),
72            Self::Internal => write!(f, "INTERNAL"),
73            Self::Unavailable => write!(f, "UNAVAILABLE"),
74            Self::DataLoss => write!(f, "DATA_LOSS"),
75            Self::Unauthenticated => write!(f, "UNAUTHENTICATED"),
76        }
77    }
78}
79
80/// Errors produced by the gRPC framing layer.
81///
82/// Marked `#[non_exhaustive]` because the crate is still evolving.
83#[derive(Debug)]
84#[non_exhaustive]
85pub enum GrpcError {
86    /// Underlying HTTP/2 error.
87    H2(ringline_h2::H2Error),
88    /// Invalid gRPC message framing (truncated prefix, etc.).
89    InvalidMessage(String),
90    /// A configured resource cap was exceeded — e.g. a single gRPC message
91    /// claiming a length over `max_message_size`, decompressed output over
92    /// `max_decompressed_size`, or a per-stream reassembly buffer over the
93    /// max-message threshold.
94    MaxSizeExceeded(String),
95    /// Connection is not ready (settings exchange incomplete).
96    NotReady,
97}
98
99impl std::fmt::Display for GrpcError {
100    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101        match self {
102            Self::H2(e) => write!(f, "h2: {e}"),
103            Self::InvalidMessage(s) => write!(f, "invalid grpc message: {s}"),
104            Self::MaxSizeExceeded(s) => write!(f, "max size exceeded: {s}"),
105            Self::NotReady => write!(f, "connection not ready"),
106        }
107    }
108}
109
110impl std::error::Error for GrpcError {}
111
112impl From<ringline_h2::H2Error> for GrpcError {
113    fn from(e: ringline_h2::H2Error) -> Self {
114        Self::H2(e)
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    #[test]
123    fn status_round_trip() {
124        for code in 0..=16u8 {
125            let status = GrpcStatus::from_u8(code);
126            assert_eq!(status as u8, code);
127        }
128    }
129
130    #[test]
131    fn unknown_status_code() {
132        assert_eq!(GrpcStatus::from_u8(99), GrpcStatus::Unknown);
133        assert_eq!(GrpcStatus::from_u8(255), GrpcStatus::Unknown);
134    }
135
136    #[test]
137    fn status_display() {
138        assert_eq!(GrpcStatus::Ok.to_string(), "OK");
139        assert_eq!(GrpcStatus::Internal.to_string(), "INTERNAL");
140        assert_eq!(GrpcStatus::Unauthenticated.to_string(), "UNAUTHENTICATED");
141    }
142
143    #[test]
144    fn error_display() {
145        let err = GrpcError::NotReady;
146        assert_eq!(err.to_string(), "connection not ready");
147
148        let err = GrpcError::InvalidMessage("bad prefix".into());
149        assert_eq!(err.to_string(), "invalid grpc message: bad prefix");
150    }
151}