tower_grpc/
status.rs

1use bytes::Bytes;
2use h2;
3use http::header::HeaderValue;
4use http::{self, HeaderMap};
5use log::{debug, trace, warn};
6use percent_encoding::{percent_decode, percent_encode, EncodeSet, DEFAULT_ENCODE_SET};
7use std::{error::Error, fmt};
8
9const GRPC_STATUS_HEADER_CODE: &str = "grpc-status";
10const GRPC_STATUS_MESSAGE_HEADER: &str = "grpc-message";
11const GRPC_STATUS_DETAILS_HEADER: &str = "grpc-status-details-bin";
12
13/// A gRPC "status" describing the result of an RPC call.
14#[derive(Clone)]
15pub struct Status {
16    /// The gRPC status code, found in the `grpc-status` header.
17    code: Code,
18    /// A relevant error message, found in the `grpc-message` header.
19    message: String,
20    /// Binary opaque details, found in the `grpc-status-details-bin` header.
21    details: Bytes,
22}
23
24/// gRPC status codes used by `Status`.
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26pub enum Code {
27    Ok = 0,
28    Cancelled = 1,
29    Unknown = 2,
30    InvalidArgument = 3,
31    DeadlineExceeded = 4,
32    NotFound = 5,
33    AlreadyExists = 6,
34    PermissionDenied = 7,
35    ResourceExhausted = 8,
36    FailedPrecondition = 9,
37    Aborted = 10,
38    OutOfRange = 11,
39    Unimplemented = 12,
40    Internal = 13,
41    Unavailable = 14,
42    DataLoss = 15,
43    Unauthenticated = 16,
44
45    // New Codes may be added in the future, so never exhaustively match!
46    #[doc(hidden)]
47    __NonExhaustive,
48}
49
50// ===== impl Status =====
51
52impl Status {
53    /// Create a new `Status` with the associated code and message.
54    pub fn new(code: Code, message: impl Into<String>) -> Status {
55        Status {
56            code,
57            message: message.into(),
58            details: Bytes::new(),
59        }
60    }
61
62    // Deprecated: this constructor encourages creating statuses with no
63    // message, hurting later debugging.
64    #[doc(hidden)]
65    #[deprecated(note = "use State::new")]
66    pub fn with_code(code: Code) -> Status {
67        Status::new(code, String::new())
68    }
69
70    // Deprecated: this constructor is overly long.
71    #[doc(hidden)]
72    #[deprecated(note = "use State::new")]
73    pub fn with_code_and_message(code: Code, message: String) -> Status {
74        Status::new(code, message)
75    }
76
77    // TODO: This should probably be made public eventually. Need to decide on
78    // the exact argument type.
79    pub(crate) fn from_error(err: &(dyn Error + 'static)) -> Status {
80        Status::try_from_error(err).unwrap_or_else(|| Status::new(Code::Unknown, err.to_string()))
81    }
82
83    fn try_from_error(err: &(dyn Error + 'static)) -> Option<Status> {
84        let mut cause = Some(err);
85
86        while let Some(err) = cause {
87            if let Some(status) = err.downcast_ref::<Status>() {
88                return Some(Status {
89                    code: status.code,
90                    message: status.message.clone(),
91                    details: status.details.clone(),
92                });
93            } else if let Some(h2) = err.downcast_ref::<h2::Error>() {
94                return Some(Status::from_h2_error(h2));
95            }
96
97            cause = err.source();
98        }
99
100        None
101    }
102
103    fn from_h2_error(err: &h2::Error) -> Status {
104        // See https://github.com/grpc/grpc/blob/3977c30/doc/PROTOCOL-HTTP2.md#errors
105        let code = match err.reason() {
106            Some(h2::Reason::NO_ERROR)
107            | Some(h2::Reason::PROTOCOL_ERROR)
108            | Some(h2::Reason::INTERNAL_ERROR)
109            | Some(h2::Reason::FLOW_CONTROL_ERROR)
110            | Some(h2::Reason::SETTINGS_TIMEOUT)
111            | Some(h2::Reason::COMPRESSION_ERROR)
112            | Some(h2::Reason::CONNECT_ERROR) => Code::Internal,
113            Some(h2::Reason::REFUSED_STREAM) => Code::Unavailable,
114            Some(h2::Reason::CANCEL) => Code::Cancelled,
115            Some(h2::Reason::ENHANCE_YOUR_CALM) => Code::ResourceExhausted,
116            Some(h2::Reason::INADEQUATE_SECURITY) => Code::PermissionDenied,
117
118            _ => Code::Unknown,
119        };
120
121        Status::new(code, format!("h2 protocol error: {}", err))
122    }
123
124    fn to_h2_error(&self) -> h2::Error {
125        // conservatively transform to h2 error codes...
126        let reason = match self.code {
127            Code::Cancelled => h2::Reason::CANCEL,
128            _ => h2::Reason::INTERNAL_ERROR,
129        };
130
131        reason.into()
132    }
133
134    pub(crate) fn map_error<E>(err: E) -> Status
135    where
136        E: Into<Box<dyn Error + Send + Sync>>,
137    {
138        Status::from_error(&*err.into())
139    }
140
141    pub(crate) fn from_header_map(header_map: &HeaderMap) -> Option<Status> {
142        header_map.get(GRPC_STATUS_HEADER_CODE).map(|code| {
143            let code = Code::from_bytes(code.as_ref());
144            let error_message = header_map
145                .get(GRPC_STATUS_MESSAGE_HEADER)
146                .map(|header| {
147                    percent_decode(header.as_bytes())
148                        .decode_utf8()
149                        .map(|cow| cow.to_string())
150                })
151                .unwrap_or_else(|| Ok(String::new()));
152            let details = header_map
153                .get(GRPC_STATUS_DETAILS_HEADER)
154                .map(|h| Bytes::from(h.as_bytes()))
155                .unwrap_or_else(Bytes::new);
156            match error_message {
157                Ok(message) => Status {
158                    code,
159                    message,
160                    details,
161                },
162                Err(err) => {
163                    warn!("Error deserializing status message header: {}", err);
164                    Status {
165                        code: Code::Unknown,
166                        message: format!("Error deserializing status message header: {}", err),
167                        details,
168                    }
169                }
170            }
171        })
172    }
173
174    /// Get the gRPC `Code` of this `Status`.
175    pub fn code(&self) -> Code {
176        self.code
177    }
178
179    /// Get the text error message of this `Status`.
180    pub fn message(&self) -> &str {
181        &self.message
182    }
183
184    /// Get the opaque error details of this `Status`.
185    pub fn details(&self) -> &[u8] {
186        &self.details
187    }
188
189    #[doc(hidden)]
190    #[deprecated(note = "use Status::message")]
191    pub fn error_message(&self) -> &str {
192        &self.message
193    }
194
195    #[doc(hidden)]
196    #[deprecated(note = "use Status::details")]
197    pub fn binary_error_details(&self) -> &Bytes {
198        &self.details
199    }
200
201    pub(crate) fn to_header_map(&self) -> Result<HeaderMap, Self> {
202        let mut header_map = HeaderMap::with_capacity(3);
203        self.add_header(&mut header_map)?;
204        Ok(header_map)
205    }
206
207    pub(crate) fn add_header(&self, header_map: &mut HeaderMap) -> Result<(), Self> {
208        header_map.insert(GRPC_STATUS_HEADER_CODE, self.code.to_header_value());
209
210        if !self.message.is_empty() {
211            let is_need_encode = self
212                .message
213                .as_bytes()
214                .iter()
215                .any(|&x| DEFAULT_ENCODE_SET.contains(x));
216            let to_write = if is_need_encode {
217                percent_encode(&self.message().as_bytes(), DEFAULT_ENCODE_SET)
218                    .to_string()
219                    .into()
220            } else {
221                Bytes::from(self.message().as_bytes())
222            };
223
224            header_map.insert(
225                GRPC_STATUS_MESSAGE_HEADER,
226                HeaderValue::from_shared(to_write).map_err(invalid_header_value_byte)?,
227            );
228        }
229
230        if !self.details.is_empty() {
231            header_map.insert(
232                GRPC_STATUS_DETAILS_HEADER,
233                HeaderValue::from_shared(self.details.clone())
234                    .map_err(invalid_header_value_byte)?,
235            );
236        }
237
238        Ok(())
239    }
240}
241
242impl fmt::Debug for Status {
243    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
244        // A manual impl to reduce the noise of frequently empty fields.
245        let mut builder = f.debug_struct("Status");
246
247        builder.field("code", &self.code);
248
249        if !self.message.is_empty() {
250            builder.field("message", &self.message);
251        }
252
253        if !self.details.is_empty() {
254            builder.field("details", &self.details);
255        }
256
257        builder.finish()
258    }
259}
260
261fn invalid_header_value_byte<Error: fmt::Display>(err: Error) -> Status {
262    debug!("Invalid header: {}", err);
263    Status::new(
264        Code::Internal,
265        "Couldn't serialize non-text grpc status header".to_string(),
266    )
267}
268
269impl From<h2::Error> for Status {
270    fn from(err: h2::Error) -> Self {
271        Status::from_h2_error(&err)
272    }
273}
274
275impl From<Status> for h2::Error {
276    fn from(status: Status) -> Self {
277        status.to_h2_error()
278    }
279}
280
281impl fmt::Display for Status {
282    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
283        write!(
284            f,
285            "grpc-status: {:?}, grpc-message: {:?}",
286            self.code(),
287            self.message()
288        )
289    }
290}
291
292impl Error for Status {}
293
294///
295/// Take the `Status` value from `trailers` if it is available, else from `status_code`.
296///
297pub(crate) fn infer_grpc_status(
298    trailers: Option<HeaderMap>,
299    status_code: http::StatusCode,
300) -> Result<(), Status> {
301    if let Some(trailers) = trailers {
302        if let Some(status) = Status::from_header_map(&trailers) {
303            if status.code() == Code::Ok {
304                return Ok(());
305            } else {
306                return Err(status);
307            }
308        }
309    }
310    trace!("trailers missing grpc-status");
311    let code = match status_code {
312        // Borrowed from https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md
313        http::StatusCode::BAD_REQUEST => Code::Internal,
314        http::StatusCode::UNAUTHORIZED => Code::Unauthenticated,
315        http::StatusCode::FORBIDDEN => Code::PermissionDenied,
316        http::StatusCode::NOT_FOUND => Code::Unimplemented,
317        http::StatusCode::TOO_MANY_REQUESTS
318        | http::StatusCode::BAD_GATEWAY
319        | http::StatusCode::SERVICE_UNAVAILABLE
320        | http::StatusCode::GATEWAY_TIMEOUT => Code::Unavailable,
321        _ => Code::Unknown,
322    };
323
324    let msg = format!(
325        "grpc-status header missing, mapped from HTTP status code {}",
326        status_code.as_u16(),
327    );
328    let status = Status::new(code, msg);
329    Err(status)
330}
331
332// ===== impl Code =====
333
334impl Code {
335    /// Get the `Code` that represents the integer, if known.
336    ///
337    /// If not known, returns `Code::Unknown` (surprise!).
338    pub fn from_i32(i: i32) -> Code {
339        Code::from(i)
340    }
341
342    pub(crate) fn from_bytes(bytes: &[u8]) -> Code {
343        match bytes.len() {
344            1 => match bytes[0] {
345                b'0' => Code::Ok,
346                b'1' => Code::Cancelled,
347                b'2' => Code::Unknown,
348                b'3' => Code::InvalidArgument,
349                b'4' => Code::DeadlineExceeded,
350                b'5' => Code::NotFound,
351                b'6' => Code::AlreadyExists,
352                b'7' => Code::PermissionDenied,
353                b'8' => Code::ResourceExhausted,
354                b'9' => Code::FailedPrecondition,
355                _ => Code::parse_err(),
356            },
357            2 => match (bytes[0], bytes[1]) {
358                (b'1', b'0') => Code::Aborted,
359                (b'1', b'1') => Code::OutOfRange,
360                (b'1', b'2') => Code::Unimplemented,
361                (b'1', b'3') => Code::Internal,
362                (b'1', b'4') => Code::Unavailable,
363                (b'1', b'5') => Code::DataLoss,
364                (b'1', b'6') => Code::Unauthenticated,
365                _ => Code::parse_err(),
366            },
367            _ => Code::parse_err(),
368        }
369    }
370
371    fn to_header_value(&self) -> HeaderValue {
372        match self {
373            Code::Ok => HeaderValue::from_static("0"),
374            Code::Cancelled => HeaderValue::from_static("1"),
375            Code::Unknown => HeaderValue::from_static("2"),
376            Code::InvalidArgument => HeaderValue::from_static("3"),
377            Code::DeadlineExceeded => HeaderValue::from_static("4"),
378            Code::NotFound => HeaderValue::from_static("5"),
379            Code::AlreadyExists => HeaderValue::from_static("6"),
380            Code::PermissionDenied => HeaderValue::from_static("7"),
381            Code::ResourceExhausted => HeaderValue::from_static("8"),
382            Code::FailedPrecondition => HeaderValue::from_static("9"),
383            Code::Aborted => HeaderValue::from_static("10"),
384            Code::OutOfRange => HeaderValue::from_static("11"),
385            Code::Unimplemented => HeaderValue::from_static("12"),
386            Code::Internal => HeaderValue::from_static("13"),
387            Code::Unavailable => HeaderValue::from_static("14"),
388            Code::DataLoss => HeaderValue::from_static("15"),
389            Code::Unauthenticated => HeaderValue::from_static("16"),
390
391            Code::__NonExhaustive => unreachable!("Code::__NonExhaustive"),
392        }
393    }
394
395    fn parse_err() -> Code {
396        trace!("error parsing grpc-status");
397        Code::Unknown
398    }
399}
400
401impl From<i32> for Code {
402    fn from(i: i32) -> Self {
403        match i {
404            0 => Code::Ok,
405            1 => Code::Cancelled,
406            2 => Code::Unknown,
407            3 => Code::InvalidArgument,
408            4 => Code::DeadlineExceeded,
409            5 => Code::NotFound,
410            6 => Code::AlreadyExists,
411            7 => Code::PermissionDenied,
412            8 => Code::ResourceExhausted,
413            9 => Code::FailedPrecondition,
414            10 => Code::Aborted,
415            11 => Code::OutOfRange,
416            12 => Code::Unimplemented,
417            13 => Code::Internal,
418            14 => Code::Unavailable,
419            15 => Code::DataLoss,
420            16 => Code::Unauthenticated,
421
422            _ => Code::Unknown,
423        }
424    }
425}
426
427#[cfg(test)]
428mod tests {
429    use super::*;
430    use crate::error::Error;
431
432    #[derive(Debug)]
433    struct Nested(Error);
434
435    impl fmt::Display for Nested {
436        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
437            write!(f, "nested error: {}", self.0)
438        }
439    }
440
441    impl std::error::Error for Nested {
442        fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
443            Some(&*self.0)
444        }
445    }
446
447    #[test]
448    fn from_error_status() {
449        let orig = Status::new(Code::OutOfRange, "weeaboo");
450        let found = Status::from_error(&orig);
451
452        assert_eq!(orig.code(), found.code());
453        assert_eq!(orig.message(), found.message());
454    }
455
456    #[test]
457    fn from_error_unknown() {
458        let orig: Error = "peek-a-boo".into();
459        let found = Status::from_error(&*orig);
460
461        assert_eq!(found.code(), Code::Unknown);
462        assert_eq!(found.message(), orig.to_string());
463    }
464
465    #[test]
466    fn from_error_nested() {
467        let orig = Nested(Box::new(Status::new(Code::OutOfRange, "weeaboo")));
468        let found = Status::from_error(&orig);
469
470        assert_eq!(found.code(), Code::OutOfRange);
471        assert_eq!(found.message(), "weeaboo");
472    }
473
474    #[test]
475    fn from_error_h2() {
476        let orig = h2::Error::from(h2::Reason::CANCEL);
477        let found = Status::from_error(&orig);
478
479        assert_eq!(found.code(), Code::Cancelled);
480    }
481
482    #[test]
483    fn to_h2_error() {
484        let orig = Status::new(Code::Cancelled, "stop eet!");
485        let err = orig.to_h2_error();
486
487        assert_eq!(err.reason(), Some(h2::Reason::CANCEL));
488    }
489
490    #[test]
491    fn code_from_i32() {
492        // This for loop should catch if we ever add a new variant and don't
493        // update From<i32>.
494        for i in 0..(Code::__NonExhaustive as i32) {
495            let code = Code::from(i);
496            assert_eq!(
497                i, code as i32,
498                "Code::from({}) returned {:?} which is {}",
499                i, code, code as i32,
500            );
501        }
502
503        assert_eq!(Code::from(-1), Code::Unknown);
504        assert_eq!(Code::from(Code::__NonExhaustive as i32), Code::Unknown);
505    }
506}