tonic/
status.rs

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