tonic/
status.rs

1use crate::body::BoxBody;
2use crate::metadata::MetadataMap;
3use bytes::Bytes;
4use http::header::{HeaderMap, HeaderValue};
5use percent_encoding::{percent_decode, percent_encode, AsciiSet, CONTROLS};
6use std::{borrow::Cow, error::Error, fmt};
7use tracing::{debug, trace, warn};
8
9const ENCODING_SET: &AsciiSet = &CONTROLS
10    .add(b' ')
11    .add(b'"')
12    .add(b'#')
13    .add(b'<')
14    .add(b'>')
15    .add(b'`')
16    .add(b'?')
17    .add(b'{')
18    .add(b'}');
19
20const GRPC_STATUS_HEADER_CODE: &str = "grpc-status";
21const GRPC_STATUS_MESSAGE_HEADER: &str = "grpc-message";
22const GRPC_STATUS_DETAILS_HEADER: &str = "grpc-status-details-bin";
23
24/// A gRPC status describing the result of an RPC call.
25///
26/// Values can be created using the `new` function or one of the specialized
27/// associated functions.
28/// ```rust
29/// # use tonic::{Status, Code};
30/// let status1 = Status::new(Code::InvalidArgument, "name is invalid");
31/// let status2 = Status::invalid_argument("name is invalid");
32///
33/// assert_eq!(status1.code(), Code::InvalidArgument);
34/// assert_eq!(status1.code(), status2.code());
35/// ```
36pub struct Status {
37    /// The gRPC status code, found in the `grpc-status` header.
38    code: Code,
39    /// A relevant error message, found in the `grpc-message` header.
40    message: String,
41    /// Binary opaque details, found in the `grpc-status-details-bin` header.
42    details: Bytes,
43    /// Custom metadata, found in the user-defined headers.
44    /// If the metadata contains any headers with names reserved either by the gRPC spec
45    /// or by `Status` fields above, they will be ignored.
46    metadata: MetadataMap,
47    /// Optional underlying error.
48    source: Option<Box<dyn Error + Send + Sync + 'static>>,
49}
50
51/// gRPC status codes used by [`Status`].
52///
53/// These variants match the [gRPC status codes].
54///
55/// [gRPC status codes]: https://github.com/grpc/grpc/blob/master/doc/statuscodes.md#status-codes-and-their-use-in-grpc
56#[derive(Clone, Copy, Debug, PartialEq, Eq)]
57pub enum Code {
58    /// The operation completed successfully.
59    Ok = 0,
60
61    /// The operation was cancelled.
62    Cancelled = 1,
63
64    /// Unknown error.
65    Unknown = 2,
66
67    /// Client specified an invalid argument.
68    InvalidArgument = 3,
69
70    /// Deadline expired before operation could complete.
71    DeadlineExceeded = 4,
72
73    /// Some requested entity was not found.
74    NotFound = 5,
75
76    /// Some entity that we attempted to create already exists.
77    AlreadyExists = 6,
78
79    /// The caller does not have permission to execute the specified operation.
80    PermissionDenied = 7,
81
82    /// Some resource has been exhausted.
83    ResourceExhausted = 8,
84
85    /// The system is not in a state required for the operation's execution.
86    FailedPrecondition = 9,
87
88    /// The operation was aborted.
89    Aborted = 10,
90
91    /// Operation was attempted past the valid range.
92    OutOfRange = 11,
93
94    /// Operation is not implemented or not supported.
95    Unimplemented = 12,
96
97    /// Internal error.
98    Internal = 13,
99
100    /// The service is currently unavailable.
101    Unavailable = 14,
102
103    /// Unrecoverable data loss or corruption.
104    DataLoss = 15,
105
106    /// The request does not have valid authentication credentials
107    Unauthenticated = 16,
108}
109
110impl Code {
111    /// Get description of this `Code`.
112    /// ```
113    /// fn make_grpc_request() -> tonic::Code {
114    ///     // ...
115    ///     tonic::Code::Ok
116    /// }
117    /// let code = make_grpc_request();
118    /// println!("Operation completed. Human readable description: {}", code.description());
119    /// ```
120    /// If you only need description in `println`, `format`, `log` and other
121    /// formatting contexts, you may want to use `Display` impl for `Code`
122    /// instead.
123    pub fn description(&self) -> &'static str {
124        match self {
125            Code::Ok => "The operation completed successfully",
126            Code::Cancelled => "The operation was cancelled",
127            Code::Unknown => "Unknown error",
128            Code::InvalidArgument => "Client specified an invalid argument",
129            Code::DeadlineExceeded => "Deadline expired before operation could complete",
130            Code::NotFound => "Some requested entity was not found",
131            Code::AlreadyExists => "Some entity that we attempted to create already exists",
132            Code::PermissionDenied => {
133                "The caller does not have permission to execute the specified operation"
134            }
135            Code::ResourceExhausted => "Some resource has been exhausted",
136            Code::FailedPrecondition => {
137                "The system is not in a state required for the operation's execution"
138            }
139            Code::Aborted => "The operation was aborted",
140            Code::OutOfRange => "Operation was attempted past the valid range",
141            Code::Unimplemented => "Operation is not implemented or not supported",
142            Code::Internal => "Internal error",
143            Code::Unavailable => "The service is currently unavailable",
144            Code::DataLoss => "Unrecoverable data loss or corruption",
145            Code::Unauthenticated => "The request does not have valid authentication credentials",
146        }
147    }
148}
149
150impl std::fmt::Display for Code {
151    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152        std::fmt::Display::fmt(self.description(), f)
153    }
154}
155
156// ===== impl Status =====
157
158impl Status {
159    /// Create a new `Status` with the associated code and message.
160    pub fn new(code: Code, message: impl Into<String>) -> Status {
161        Status {
162            code,
163            message: message.into(),
164            details: Bytes::new(),
165            metadata: MetadataMap::new(),
166            source: None,
167        }
168    }
169
170    /// The operation completed successfully.
171    pub fn ok(message: impl Into<String>) -> Status {
172        Status::new(Code::Ok, message)
173    }
174
175    /// The operation was cancelled (typically by the caller).
176    pub fn cancelled(message: impl Into<String>) -> Status {
177        Status::new(Code::Cancelled, message)
178    }
179
180    /// Unknown error. An example of where this error may be returned is if a
181    /// `Status` value received from another address space belongs to an error-space
182    /// that is not known in this address space. Also errors raised by APIs that
183    /// do not return enough error information may be converted to this error.
184    pub fn unknown(message: impl Into<String>) -> Status {
185        Status::new(Code::Unknown, message)
186    }
187
188    /// Client specified an invalid argument. Note that this differs from
189    /// `FailedPrecondition`. `InvalidArgument` indicates arguments that are
190    /// problematic regardless of the state of the system (e.g., a malformed file
191    /// name).
192    pub fn invalid_argument(message: impl Into<String>) -> Status {
193        Status::new(Code::InvalidArgument, message)
194    }
195
196    /// Deadline expired before operation could complete. For operations that
197    /// change the state of the system, this error may be returned even if the
198    /// operation has completed successfully. For example, a successful response
199    /// from a server could have been delayed long enough for the deadline to
200    /// expire.
201    pub fn deadline_exceeded(message: impl Into<String>) -> Status {
202        Status::new(Code::DeadlineExceeded, message)
203    }
204
205    /// Some requested entity (e.g., file or directory) was not found.
206    pub fn not_found(message: impl Into<String>) -> Status {
207        Status::new(Code::NotFound, message)
208    }
209
210    /// Some entity that we attempted to create (e.g., file or directory) already
211    /// exists.
212    pub fn already_exists(message: impl Into<String>) -> Status {
213        Status::new(Code::AlreadyExists, message)
214    }
215
216    /// The caller does not have permission to execute the specified operation.
217    /// `PermissionDenied` must not be used for rejections caused by exhausting
218    /// some resource (use `ResourceExhausted` instead for those errors).
219    /// `PermissionDenied` must not be used if the caller cannot be identified
220    /// (use `Unauthenticated` instead for those errors).
221    pub fn permission_denied(message: impl Into<String>) -> Status {
222        Status::new(Code::PermissionDenied, message)
223    }
224
225    /// Some resource has been exhausted, perhaps a per-user quota, or perhaps
226    /// the entire file system is out of space.
227    pub fn resource_exhausted(message: impl Into<String>) -> Status {
228        Status::new(Code::ResourceExhausted, message)
229    }
230
231    /// Operation was rejected because the system is not in a state required for
232    /// the operation's execution. For example, directory to be deleted may be
233    /// non-empty, an rmdir operation is applied to a non-directory, etc.
234    ///
235    /// A litmus test that may help a service implementor in deciding between
236    /// `FailedPrecondition`, `Aborted`, and `Unavailable`:
237    /// (a) Use `Unavailable` if the client can retry just the failing call.
238    /// (b) Use `Aborted` if the client should retry at a higher-level (e.g.,
239    ///     restarting a read-modify-write sequence).
240    /// (c) Use `FailedPrecondition` if the client should not retry until the
241    ///     system state has been explicitly fixed.  E.g., if an "rmdir" fails
242    ///     because the directory is non-empty, `FailedPrecondition` should be
243    ///     returned since the client should not retry unless they have first
244    ///     fixed up the directory by deleting files from it.
245    pub fn failed_precondition(message: impl Into<String>) -> Status {
246        Status::new(Code::FailedPrecondition, message)
247    }
248
249    /// The operation was aborted, typically due to a concurrency issue like
250    /// sequencer check failures, transaction aborts, etc.
251    ///
252    /// See litmus test above for deciding between `FailedPrecondition`,
253    /// `Aborted`, and `Unavailable`.
254    pub fn aborted(message: impl Into<String>) -> Status {
255        Status::new(Code::Aborted, message)
256    }
257
258    /// Operation was attempted past the valid range. E.g., seeking or reading
259    /// past end of file.
260    ///
261    /// Unlike `InvalidArgument`, this error indicates a problem that may be
262    /// fixed if the system state changes. For example, a 32-bit file system will
263    /// generate `InvalidArgument if asked to read at an offset that is not in the
264    /// range [0,2^32-1], but it will generate `OutOfRange` if asked to read from
265    /// an offset past the current file size.
266    ///
267    /// There is a fair bit of overlap between `FailedPrecondition` and
268    /// `OutOfRange`. We recommend using `OutOfRange` (the more specific error)
269    /// when it applies so that callers who are iterating through a space can
270    /// easily look for an `OutOfRange` error to detect when they are done.
271    pub fn out_of_range(message: impl Into<String>) -> Status {
272        Status::new(Code::OutOfRange, message)
273    }
274
275    /// Operation is not implemented or not supported/enabled in this service.
276    pub fn unimplemented(message: impl Into<String>) -> Status {
277        Status::new(Code::Unimplemented, message)
278    }
279
280    /// Internal errors. Means some invariants expected by underlying system has
281    /// been broken. If you see one of these errors, something is very broken.
282    pub fn internal(message: impl Into<String>) -> Status {
283        Status::new(Code::Internal, message)
284    }
285
286    /// The service is currently unavailable.  This is a most likely a transient
287    /// condition and may be corrected by retrying with a back-off.
288    ///
289    /// See litmus test above for deciding between `FailedPrecondition`,
290    /// `Aborted`, and `Unavailable`.
291    pub fn unavailable(message: impl Into<String>) -> Status {
292        Status::new(Code::Unavailable, message)
293    }
294
295    /// Unrecoverable data loss or corruption.
296    pub fn data_loss(message: impl Into<String>) -> Status {
297        Status::new(Code::DataLoss, message)
298    }
299
300    /// The request does not have valid authentication credentials for the
301    /// operation.
302    pub fn unauthenticated(message: impl Into<String>) -> Status {
303        Status::new(Code::Unauthenticated, message)
304    }
305
306    #[cfg_attr(not(feature = "transport"), allow(dead_code))]
307    pub(crate) fn from_error(err: Box<dyn Error + Send + Sync + 'static>) -> Status {
308        Status::try_from_error(err).unwrap_or_else(|err| {
309            let mut status = Status::new(Code::Unknown, err.to_string());
310            status.source = Some(err);
311            status
312        })
313    }
314
315    pub(crate) fn try_from_error(
316        err: Box<dyn Error + Send + Sync + 'static>,
317    ) -> Result<Status, Box<dyn Error + Send + Sync + 'static>> {
318        let err = match err.downcast::<Status>() {
319            Ok(status) => {
320                return Ok(*status);
321            }
322            Err(err) => err,
323        };
324
325        #[cfg(feature = "transport")]
326        let err = match err.downcast::<h2::Error>() {
327            Ok(h2) => {
328                return Ok(Status::from_h2_error(&*h2));
329            }
330            Err(err) => err,
331        };
332
333        if let Some(mut status) = find_status_in_source_chain(&*err) {
334            status.source = Some(err);
335            return Ok(status);
336        }
337
338        Err(err)
339    }
340
341    // FIXME: bubble this into `transport` and expose generic http2 reasons.
342    #[cfg(feature = "transport")]
343    fn from_h2_error(err: &h2::Error) -> Status {
344        // See https://github.com/grpc/grpc/blob/3977c30/doc/PROTOCOL-HTTP2.md#errors
345        let code = match err.reason() {
346            Some(h2::Reason::NO_ERROR)
347            | Some(h2::Reason::PROTOCOL_ERROR)
348            | Some(h2::Reason::INTERNAL_ERROR)
349            | Some(h2::Reason::FLOW_CONTROL_ERROR)
350            | Some(h2::Reason::SETTINGS_TIMEOUT)
351            | Some(h2::Reason::COMPRESSION_ERROR)
352            | Some(h2::Reason::CONNECT_ERROR) => Code::Internal,
353            Some(h2::Reason::REFUSED_STREAM) => Code::Unavailable,
354            Some(h2::Reason::CANCEL) => Code::Cancelled,
355            Some(h2::Reason::ENHANCE_YOUR_CALM) => Code::ResourceExhausted,
356            Some(h2::Reason::INADEQUATE_SECURITY) => Code::PermissionDenied,
357
358            _ => Code::Unknown,
359        };
360
361        let mut status = Self::new(code, format!("h2 protocol error: {}", err));
362        let error = err
363            .reason()
364            .map(h2::Error::from)
365            .map(|err| Box::new(err) as Box<dyn Error + Send + Sync + 'static>);
366        status.source = error;
367        status
368    }
369
370    #[cfg(feature = "transport")]
371    fn to_h2_error(&self) -> h2::Error {
372        // conservatively transform to h2 error codes...
373        let reason = match self.code {
374            Code::Cancelled => h2::Reason::CANCEL,
375            _ => h2::Reason::INTERNAL_ERROR,
376        };
377
378        reason.into()
379    }
380
381    /// Handles hyper errors specifically, which expose a number of different parameters about the
382    /// http stream's error: https://docs.rs/hyper/0.14.11/hyper/struct.Error.html.
383    ///
384    /// Returns Some if there's a way to handle the error, or None if the information from this
385    /// hyper error, but perhaps not its source, should be ignored.
386    #[cfg(feature = "transport")]
387    fn from_hyper_error(err: &hyper::Error) -> Option<Status> {
388        // is_timeout results from hyper's keep-alive logic
389        // (https://docs.rs/hyper/0.14.11/src/hyper/error.rs.html#192-194).  Per the grpc spec
390        // > An expired client initiated PING will cause all calls to be closed with an UNAVAILABLE
391        // > status. Note that the frequency of PINGs is highly dependent on the network
392        // > environment, implementations are free to adjust PING frequency based on network and
393        // > application requirements, which is why it's mapped to unavailable here.
394        //
395        // Likewise, if we are unable to connect to the server, map this to UNAVAILABLE.  This is
396        // consistent with the behavior of a C++ gRPC client when the server is not running, and
397        // matches the spec of:
398        // > The service is currently unavailable. This is most likely a transient condition that
399        // > can be corrected if retried with a backoff.
400        if err.is_timeout() || err.is_connect() {
401            return Some(Status::unavailable(err.to_string()));
402        }
403        None
404    }
405
406    pub(crate) fn map_error<E>(err: E) -> Status
407    where
408        E: Into<Box<dyn Error + Send + Sync>>,
409    {
410        let err: Box<dyn Error + Send + Sync> = err.into();
411        Status::from_error(err)
412    }
413
414    /// Extract a `Status` from a hyper `HeaderMap`.
415    pub fn from_header_map(header_map: &HeaderMap) -> Option<Status> {
416        header_map.get(GRPC_STATUS_HEADER_CODE).map(|code| {
417            let code = Code::from_bytes(code.as_ref());
418            let error_message = header_map
419                .get(GRPC_STATUS_MESSAGE_HEADER)
420                .map(|header| {
421                    percent_decode(header.as_bytes())
422                        .decode_utf8()
423                        .map(|cow| cow.to_string())
424                })
425                .unwrap_or_else(|| Ok(String::new()));
426
427            let details = header_map
428                .get(GRPC_STATUS_DETAILS_HEADER)
429                .map(|h| {
430                    base64::decode(h.as_bytes())
431                        .expect("Invalid status header, expected base64 encoded value")
432                })
433                .map(Bytes::from)
434                .unwrap_or_else(Bytes::new);
435
436            let mut other_headers = header_map.clone();
437            other_headers.remove(GRPC_STATUS_HEADER_CODE);
438            other_headers.remove(GRPC_STATUS_MESSAGE_HEADER);
439            other_headers.remove(GRPC_STATUS_DETAILS_HEADER);
440
441            match error_message {
442                Ok(message) => Status {
443                    code,
444                    message,
445                    details,
446                    metadata: MetadataMap::from_headers(other_headers),
447                    source: None,
448                },
449                Err(err) => {
450                    warn!("Error deserializing status message header: {}", err);
451                    Status {
452                        code: Code::Unknown,
453                        message: format!("Error deserializing status message header: {}", err),
454                        details,
455                        metadata: MetadataMap::from_headers(other_headers),
456                        source: None,
457                    }
458                }
459            }
460        })
461    }
462
463    /// Get the gRPC `Code` of this `Status`.
464    pub fn code(&self) -> Code {
465        self.code
466    }
467
468    /// Get the text error message of this `Status`.
469    pub fn message(&self) -> &str {
470        &self.message
471    }
472
473    /// Get the opaque error details of this `Status`.
474    pub fn details(&self) -> &[u8] {
475        &self.details
476    }
477
478    /// Get a reference to the custom metadata.
479    pub fn metadata(&self) -> &MetadataMap {
480        &self.metadata
481    }
482
483    /// Get a mutable reference to the custom metadata.
484    pub fn metadata_mut(&mut self) -> &mut MetadataMap {
485        &mut self.metadata
486    }
487
488    pub(crate) fn to_header_map(&self) -> Result<HeaderMap, Self> {
489        let mut header_map = HeaderMap::with_capacity(3 + self.metadata.len());
490        self.add_header(&mut header_map)?;
491        Ok(header_map)
492    }
493
494    pub(crate) fn add_header(&self, header_map: &mut HeaderMap) -> Result<(), Self> {
495        header_map.extend(self.metadata.clone().into_sanitized_headers());
496
497        header_map.insert(GRPC_STATUS_HEADER_CODE, self.code.to_header_value());
498
499        if !self.message.is_empty() {
500            let to_write = Bytes::copy_from_slice(
501                Cow::from(percent_encode(&self.message().as_bytes(), ENCODING_SET)).as_bytes(),
502            );
503
504            header_map.insert(
505                GRPC_STATUS_MESSAGE_HEADER,
506                HeaderValue::from_maybe_shared(to_write).map_err(invalid_header_value_byte)?,
507            );
508        }
509
510        if !self.details.is_empty() {
511            let details = base64::encode_config(&self.details[..], base64::STANDARD_NO_PAD);
512
513            header_map.insert(
514                GRPC_STATUS_DETAILS_HEADER,
515                HeaderValue::from_maybe_shared(details).map_err(invalid_header_value_byte)?,
516            );
517        }
518
519        Ok(())
520    }
521
522    /// Create a new `Status` with the associated code, message, and binary details field.
523    pub fn with_details(code: Code, message: impl Into<String>, details: Bytes) -> Status {
524        Self::with_details_and_metadata(code, message, details, MetadataMap::new())
525    }
526
527    /// Create a new `Status` with the associated code, message, and custom metadata
528    pub fn with_metadata(code: Code, message: impl Into<String>, metadata: MetadataMap) -> Status {
529        Self::with_details_and_metadata(code, message, Bytes::new(), metadata)
530    }
531
532    /// Create a new `Status` with the associated code, message, binary details field and custom metadata
533    pub fn with_details_and_metadata(
534        code: Code,
535        message: impl Into<String>,
536        details: Bytes,
537        metadata: MetadataMap,
538    ) -> Status {
539        Status {
540            code,
541            message: message.into(),
542            details,
543            metadata,
544            source: None,
545        }
546    }
547
548    #[allow(clippy::wrong_self_convention)]
549    /// Build an `http::Response` from the given `Status`.
550    pub fn to_http(self) -> http::Response<BoxBody> {
551        let (mut parts, _body) = http::Response::new(()).into_parts();
552
553        parts.headers.insert(
554            http::header::CONTENT_TYPE,
555            http::header::HeaderValue::from_static("application/grpc"),
556        );
557
558        self.add_header(&mut parts.headers).unwrap();
559
560        http::Response::from_parts(parts, crate::body::empty_body())
561    }
562}
563
564fn find_status_in_source_chain(err: &(dyn Error + 'static)) -> Option<Status> {
565    let mut source = Some(err);
566
567    while let Some(err) = source {
568        if let Some(status) = err.downcast_ref::<Status>() {
569            return Some(Status {
570                code: status.code,
571                message: status.message.clone(),
572                details: status.details.clone(),
573                metadata: status.metadata.clone(),
574                // Since `Status` is not `Clone`, any `source` on the original Status
575                // cannot be cloned so must remain with the original `Status`.
576                source: None,
577            });
578        }
579
580        #[cfg(feature = "transport")]
581        if let Some(timeout) = err.downcast_ref::<crate::transport::TimeoutExpired>() {
582            return Some(Status::cancelled(timeout.to_string()));
583        }
584
585        #[cfg(feature = "transport")]
586        if let Some(hyper) = err
587            .downcast_ref::<hyper::Error>()
588            .and_then(Status::from_hyper_error)
589        {
590            return Some(hyper);
591        }
592
593        source = err.source();
594    }
595
596    None
597}
598
599impl fmt::Debug for Status {
600    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
601        // A manual impl to reduce the noise of frequently empty fields.
602        let mut builder = f.debug_struct("Status");
603
604        builder.field("code", &self.code);
605
606        if !self.message.is_empty() {
607            builder.field("message", &self.message);
608        }
609
610        if !self.details.is_empty() {
611            builder.field("details", &self.details);
612        }
613
614        if !self.metadata.is_empty() {
615            builder.field("metadata", &self.metadata);
616        }
617
618        builder.field("source", &self.source);
619
620        builder.finish()
621    }
622}
623
624fn invalid_header_value_byte<Error: fmt::Display>(err: Error) -> Status {
625    debug!("Invalid header: {}", err);
626    Status::new(
627        Code::Internal,
628        "Couldn't serialize non-text grpc status header".to_string(),
629    )
630}
631
632#[cfg(feature = "transport")]
633impl From<h2::Error> for Status {
634    fn from(err: h2::Error) -> Self {
635        Status::from_h2_error(&err)
636    }
637}
638
639#[cfg(feature = "transport")]
640impl From<Status> for h2::Error {
641    fn from(status: Status) -> Self {
642        status.to_h2_error()
643    }
644}
645
646impl From<std::io::Error> for Status {
647    fn from(err: std::io::Error) -> Self {
648        use std::io::ErrorKind;
649        let code = match err.kind() {
650            ErrorKind::BrokenPipe
651            | ErrorKind::WouldBlock
652            | ErrorKind::WriteZero
653            | ErrorKind::Interrupted => Code::Internal,
654            ErrorKind::ConnectionRefused
655            | ErrorKind::ConnectionReset
656            | ErrorKind::NotConnected
657            | ErrorKind::AddrInUse
658            | ErrorKind::AddrNotAvailable => Code::Unavailable,
659            ErrorKind::AlreadyExists => Code::AlreadyExists,
660            ErrorKind::ConnectionAborted => Code::Aborted,
661            ErrorKind::InvalidData => Code::DataLoss,
662            ErrorKind::InvalidInput => Code::InvalidArgument,
663            ErrorKind::NotFound => Code::NotFound,
664            ErrorKind::PermissionDenied => Code::PermissionDenied,
665            ErrorKind::TimedOut => Code::DeadlineExceeded,
666            ErrorKind::UnexpectedEof => Code::OutOfRange,
667            _ => Code::Unknown,
668        };
669        Status::new(code, err.to_string())
670    }
671}
672
673impl fmt::Display for Status {
674    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
675        write!(
676            f,
677            "status: {:?}, message: {:?}, details: {:?}, metadata: {:?}",
678            self.code(),
679            self.message(),
680            self.details(),
681            self.metadata(),
682        )
683    }
684}
685
686impl Error for Status {
687    fn source(&self) -> Option<&(dyn Error + 'static)> {
688        self.source.as_ref().map(|err| (&**err) as _)
689    }
690}
691
692///
693/// Take the `Status` value from `trailers` if it is available, else from `status_code`.
694///
695pub(crate) fn infer_grpc_status(
696    trailers: Option<&HeaderMap>,
697    status_code: http::StatusCode,
698) -> Result<(), Option<Status>> {
699    if let Some(trailers) = trailers {
700        if let Some(status) = Status::from_header_map(&trailers) {
701            if status.code() == Code::Ok {
702                return Ok(());
703            } else {
704                return Err(status.into());
705            }
706        }
707    }
708    trace!("trailers missing grpc-status");
709    let code = match status_code {
710        // Borrowed from https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md
711        http::StatusCode::BAD_REQUEST => Code::Internal,
712        http::StatusCode::UNAUTHORIZED => Code::Unauthenticated,
713        http::StatusCode::FORBIDDEN => Code::PermissionDenied,
714        http::StatusCode::NOT_FOUND => Code::Unimplemented,
715        http::StatusCode::TOO_MANY_REQUESTS
716        | http::StatusCode::BAD_GATEWAY
717        | http::StatusCode::SERVICE_UNAVAILABLE
718        | http::StatusCode::GATEWAY_TIMEOUT => Code::Unavailable,
719        // We got a 200 but no trailers, we can infer that this request is finished.
720        //
721        // This can happen when a streaming response sends two Status but
722        // gRPC requires that we end the stream after the first status.
723        //
724        // https://github.com/hyperium/tonic/issues/681
725        http::StatusCode::OK => return Err(None),
726        _ => Code::Unknown,
727    };
728
729    let msg = format!(
730        "grpc-status header missing, mapped from HTTP status code {}",
731        status_code.as_u16(),
732    );
733    let status = Status::new(code, msg);
734    Err(status.into())
735}
736
737// ===== impl Code =====
738
739impl Code {
740    /// Get the `Code` that represents the integer, if known.
741    ///
742    /// If not known, returns `Code::Unknown` (surprise!).
743    pub fn from_i32(i: i32) -> Code {
744        Code::from(i)
745    }
746
747    /// Convert the string representation of a `Code` (as stored, for example, in the `grpc-status`
748    /// header in a response) into a `Code`. Returns `Code::Unknown` if the code string is not a
749    /// valid gRPC status code.
750    pub fn from_bytes(bytes: &[u8]) -> Code {
751        match bytes.len() {
752            1 => match bytes[0] {
753                b'0' => Code::Ok,
754                b'1' => Code::Cancelled,
755                b'2' => Code::Unknown,
756                b'3' => Code::InvalidArgument,
757                b'4' => Code::DeadlineExceeded,
758                b'5' => Code::NotFound,
759                b'6' => Code::AlreadyExists,
760                b'7' => Code::PermissionDenied,
761                b'8' => Code::ResourceExhausted,
762                b'9' => Code::FailedPrecondition,
763                _ => Code::parse_err(),
764            },
765            2 => match (bytes[0], bytes[1]) {
766                (b'1', b'0') => Code::Aborted,
767                (b'1', b'1') => Code::OutOfRange,
768                (b'1', b'2') => Code::Unimplemented,
769                (b'1', b'3') => Code::Internal,
770                (b'1', b'4') => Code::Unavailable,
771                (b'1', b'5') => Code::DataLoss,
772                (b'1', b'6') => Code::Unauthenticated,
773                _ => Code::parse_err(),
774            },
775            _ => Code::parse_err(),
776        }
777    }
778
779    fn to_header_value(self) -> HeaderValue {
780        match self {
781            Code::Ok => HeaderValue::from_static("0"),
782            Code::Cancelled => HeaderValue::from_static("1"),
783            Code::Unknown => HeaderValue::from_static("2"),
784            Code::InvalidArgument => HeaderValue::from_static("3"),
785            Code::DeadlineExceeded => HeaderValue::from_static("4"),
786            Code::NotFound => HeaderValue::from_static("5"),
787            Code::AlreadyExists => HeaderValue::from_static("6"),
788            Code::PermissionDenied => HeaderValue::from_static("7"),
789            Code::ResourceExhausted => HeaderValue::from_static("8"),
790            Code::FailedPrecondition => HeaderValue::from_static("9"),
791            Code::Aborted => HeaderValue::from_static("10"),
792            Code::OutOfRange => HeaderValue::from_static("11"),
793            Code::Unimplemented => HeaderValue::from_static("12"),
794            Code::Internal => HeaderValue::from_static("13"),
795            Code::Unavailable => HeaderValue::from_static("14"),
796            Code::DataLoss => HeaderValue::from_static("15"),
797            Code::Unauthenticated => HeaderValue::from_static("16"),
798        }
799    }
800
801    fn parse_err() -> Code {
802        trace!("error parsing grpc-status");
803        Code::Unknown
804    }
805}
806
807impl From<i32> for Code {
808    fn from(i: i32) -> Self {
809        match i {
810            0 => Code::Ok,
811            1 => Code::Cancelled,
812            2 => Code::Unknown,
813            3 => Code::InvalidArgument,
814            4 => Code::DeadlineExceeded,
815            5 => Code::NotFound,
816            6 => Code::AlreadyExists,
817            7 => Code::PermissionDenied,
818            8 => Code::ResourceExhausted,
819            9 => Code::FailedPrecondition,
820            10 => Code::Aborted,
821            11 => Code::OutOfRange,
822            12 => Code::Unimplemented,
823            13 => Code::Internal,
824            14 => Code::Unavailable,
825            15 => Code::DataLoss,
826            16 => Code::Unauthenticated,
827
828            _ => Code::Unknown,
829        }
830    }
831}
832
833impl From<Code> for i32 {
834    #[inline]
835    fn from(code: Code) -> i32 {
836        code as i32
837    }
838}
839
840#[cfg(test)]
841mod tests {
842    use super::*;
843    use crate::Error;
844
845    #[derive(Debug)]
846    struct Nested(Error);
847
848    impl fmt::Display for Nested {
849        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
850            write!(f, "nested error: {}", self.0)
851        }
852    }
853
854    impl std::error::Error for Nested {
855        fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
856            Some(&*self.0)
857        }
858    }
859
860    #[test]
861    fn from_error_status() {
862        let orig = Status::new(Code::OutOfRange, "weeaboo");
863        let found = Status::from_error(Box::new(orig));
864
865        assert_eq!(found.code(), Code::OutOfRange);
866        assert_eq!(found.message(), "weeaboo");
867    }
868
869    #[test]
870    fn from_error_unknown() {
871        let orig: Error = "peek-a-boo".into();
872        let found = Status::from_error(orig);
873
874        assert_eq!(found.code(), Code::Unknown);
875        assert_eq!(found.message(), "peek-a-boo".to_string());
876    }
877
878    #[test]
879    fn from_error_nested() {
880        let orig = Nested(Box::new(Status::new(Code::OutOfRange, "weeaboo")));
881        let found = Status::from_error(Box::new(orig));
882
883        assert_eq!(found.code(), Code::OutOfRange);
884        assert_eq!(found.message(), "weeaboo");
885    }
886
887    #[test]
888    #[cfg(feature = "transport")]
889    fn from_error_h2() {
890        use std::error::Error as _;
891
892        let orig = h2::Error::from(h2::Reason::CANCEL);
893        let found = Status::from_error(Box::new(orig));
894
895        assert_eq!(found.code(), Code::Cancelled);
896
897        let source = found
898            .source()
899            .and_then(|err| err.downcast_ref::<h2::Error>())
900            .unwrap();
901        assert_eq!(source.reason(), Some(h2::Reason::CANCEL));
902    }
903
904    #[test]
905    #[cfg(feature = "transport")]
906    fn to_h2_error() {
907        let orig = Status::new(Code::Cancelled, "stop eet!");
908        let err = orig.to_h2_error();
909
910        assert_eq!(err.reason(), Some(h2::Reason::CANCEL));
911    }
912
913    #[test]
914    fn code_from_i32() {
915        // This for loop should catch if we ever add a new variant and don't
916        // update From<i32>.
917        for i in 0..(Code::Unauthenticated as i32) {
918            let code = Code::from(i);
919            assert_eq!(
920                i, code as i32,
921                "Code::from({}) returned {:?} which is {}",
922                i, code, code as i32,
923            );
924        }
925
926        assert_eq!(Code::from(-1), Code::Unknown);
927    }
928
929    #[test]
930    fn constructors() {
931        assert_eq!(Status::ok("").code(), Code::Ok);
932        assert_eq!(Status::cancelled("").code(), Code::Cancelled);
933        assert_eq!(Status::unknown("").code(), Code::Unknown);
934        assert_eq!(Status::invalid_argument("").code(), Code::InvalidArgument);
935        assert_eq!(Status::deadline_exceeded("").code(), Code::DeadlineExceeded);
936        assert_eq!(Status::not_found("").code(), Code::NotFound);
937        assert_eq!(Status::already_exists("").code(), Code::AlreadyExists);
938        assert_eq!(Status::permission_denied("").code(), Code::PermissionDenied);
939        assert_eq!(
940            Status::resource_exhausted("").code(),
941            Code::ResourceExhausted
942        );
943        assert_eq!(
944            Status::failed_precondition("").code(),
945            Code::FailedPrecondition
946        );
947        assert_eq!(Status::aborted("").code(), Code::Aborted);
948        assert_eq!(Status::out_of_range("").code(), Code::OutOfRange);
949        assert_eq!(Status::unimplemented("").code(), Code::Unimplemented);
950        assert_eq!(Status::internal("").code(), Code::Internal);
951        assert_eq!(Status::unavailable("").code(), Code::Unavailable);
952        assert_eq!(Status::data_loss("").code(), Code::DataLoss);
953        assert_eq!(Status::unauthenticated("").code(), Code::Unauthenticated);
954    }
955
956    #[test]
957    fn details() {
958        const DETAILS: &[u8] = &[0, 2, 3];
959
960        let status = Status::with_details(Code::Unavailable, "some message", DETAILS.into());
961
962        assert_eq!(status.details(), DETAILS);
963
964        let header_map = status.to_header_map().unwrap();
965
966        let b64_details = base64::encode_config(DETAILS, base64::STANDARD_NO_PAD);
967
968        assert_eq!(header_map[super::GRPC_STATUS_DETAILS_HEADER], b64_details);
969
970        let status = Status::from_header_map(&header_map).unwrap();
971
972        assert_eq!(status.details(), DETAILS);
973    }
974}