Skip to main content

tonic/
status.rs

1use crate::metadata::GRPC_CONTENT_TYPE;
2use crate::metadata::MetadataMap;
3use base64::Engine as _;
4use bytes::Bytes;
5use http::{
6    HeaderName,
7    header::{HeaderMap, HeaderValue},
8};
9use percent_encoding::{AsciiSet, CONTROLS, percent_decode, percent_encode};
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            // NO_ERROR on a RST_STREAM means the peer reset the stream without a gRPC status.
397            // Per the spec this is still a protocol violation and must be mapped to INTERNAL.
398            Some(h2::Reason::NO_ERROR)
399            | Some(h2::Reason::PROTOCOL_ERROR)
400            | Some(h2::Reason::INTERNAL_ERROR)
401            | Some(h2::Reason::FLOW_CONTROL_ERROR)
402            | Some(h2::Reason::SETTINGS_TIMEOUT)
403            | Some(h2::Reason::COMPRESSION_ERROR)
404            | Some(h2::Reason::CONNECT_ERROR) => Code::Internal,
405            Some(h2::Reason::REFUSED_STREAM) => Code::Unavailable,
406            Some(h2::Reason::CANCEL) => Code::Cancelled,
407            Some(h2::Reason::ENHANCE_YOUR_CALM) => Code::ResourceExhausted,
408            Some(h2::Reason::INADEQUATE_SECURITY) => Code::PermissionDenied,
409
410            _ => Code::Unknown,
411        }
412    }
413
414    #[cfg(feature = "server")]
415    fn to_h2_error(&self) -> h2::Error {
416        // conservatively transform to h2 error codes...
417        let reason = match self.code() {
418            Code::Cancelled => h2::Reason::CANCEL,
419            _ => h2::Reason::INTERNAL_ERROR,
420        };
421
422        reason.into()
423    }
424
425    /// Handles hyper errors specifically, which expose a number of different parameters about the
426    /// http stream's error: https://docs.rs/hyper/0.14.11/hyper/struct.Error.html.
427    ///
428    /// Returns Some if there's a way to handle the error, or None if the information from this
429    /// hyper error, but perhaps not its source, should be ignored.
430    #[cfg(any(feature = "server", feature = "channel"))]
431    fn from_hyper_error(err: &hyper::Error) -> Option<Status> {
432        // is_timeout results from hyper's keep-alive logic
433        // (https://docs.rs/hyper/0.14.11/src/hyper/error.rs.html#192-194).  Per the grpc spec
434        // > An expired client initiated PING will cause all calls to be closed with an UNAVAILABLE
435        // > status. Note that the frequency of PINGs is highly dependent on the network
436        // > environment, implementations are free to adjust PING frequency based on network and
437        // > application requirements, which is why it's mapped to unavailable here.
438        if err.is_timeout() {
439            return Some(Status::unavailable(err.to_string()));
440        }
441
442        if err.is_canceled() {
443            return Some(Status::cancelled(err.to_string()));
444        }
445
446        #[cfg(feature = "server")]
447        if let Some(h2_err) = err.source().and_then(|e| e.downcast_ref::<h2::Error>()) {
448            let code = Status::code_from_h2(h2_err);
449            let status = Self::new(code, format!("h2 protocol error: {err}"));
450
451            return Some(status);
452        }
453
454        None
455    }
456
457    pub(crate) fn map_error<E>(err: E) -> Status
458    where
459        E: Into<Box<dyn Error + Send + Sync>>,
460    {
461        let err: Box<dyn Error + Send + Sync> = err.into();
462        Status::from_error(err)
463    }
464
465    /// Extract a `Status` from a hyper `HeaderMap`.
466    pub fn from_header_map(header_map: &HeaderMap) -> Option<Status> {
467        let code = Code::from_bytes(header_map.get(Self::GRPC_STATUS)?.as_ref());
468
469        let error_message = match header_map.get(Self::GRPC_MESSAGE) {
470            Some(header) => percent_decode(header.as_bytes())
471                .decode_utf8()
472                .map(|cow| cow.to_string()),
473            None => Ok(String::new()),
474        };
475
476        let details = match header_map.get(Self::GRPC_STATUS_DETAILS) {
477            Some(header) => crate::util::base64::STANDARD
478                .decode(header.as_bytes())
479                .expect("Invalid status header, expected base64 encoded value")
480                .into(),
481            None => Bytes::new(),
482        };
483
484        let other_headers = {
485            let mut header_map = header_map.clone();
486            header_map.remove(Self::GRPC_STATUS);
487            header_map.remove(Self::GRPC_MESSAGE);
488            header_map.remove(Self::GRPC_STATUS_DETAILS);
489            header_map
490        };
491
492        let (code, message) = match error_message {
493            Ok(message) => (code, message),
494            Err(e) => {
495                let error_message = format!("Error deserializing status message header: {e}");
496                warn!(error_message);
497                (Code::Unknown, error_message)
498            }
499        };
500
501        Some(
502            StatusInner {
503                code,
504                message,
505                details,
506                metadata: MetadataMap::from_headers(other_headers),
507                source: None,
508            }
509            .into_status(),
510        )
511    }
512
513    /// Get the gRPC `Code` of this `Status`.
514    pub fn code(&self) -> Code {
515        self.0.code
516    }
517
518    /// Get the text error message of this `Status`.
519    pub fn message(&self) -> &str {
520        &self.0.message
521    }
522
523    /// Get the opaque error details of this `Status`.
524    pub fn details(&self) -> &[u8] {
525        &self.0.details
526    }
527
528    /// Get a reference to the custom metadata.
529    pub fn metadata(&self) -> &MetadataMap {
530        &self.0.metadata
531    }
532
533    /// Get a mutable reference to the custom metadata.
534    pub fn metadata_mut(&mut self) -> &mut MetadataMap {
535        &mut self.0.metadata
536    }
537
538    pub(crate) fn to_header_map(&self) -> Result<HeaderMap, Self> {
539        let mut header_map = HeaderMap::with_capacity(3 + self.0.metadata.len());
540        self.add_header(&mut header_map)?;
541        Ok(header_map)
542    }
543
544    /// Add headers from this `Status` into `header_map`.
545    pub fn add_header(&self, header_map: &mut HeaderMap) -> Result<(), Self> {
546        header_map.extend(self.0.metadata.clone().into_sanitized_headers());
547
548        header_map.insert(Self::GRPC_STATUS, self.0.code.to_header_value());
549
550        if !self.0.message.is_empty() {
551            let to_write = Bytes::copy_from_slice(
552                Cow::from(percent_encode(self.message().as_bytes(), ENCODING_SET)).as_bytes(),
553            );
554
555            header_map.insert(
556                Self::GRPC_MESSAGE,
557                HeaderValue::from_maybe_shared(to_write).map_err(invalid_header_value_byte)?,
558            );
559        }
560
561        if !self.0.details.is_empty() {
562            let details = crate::util::base64::STANDARD_NO_PAD.encode(&self.0.details[..]);
563
564            header_map.insert(
565                Self::GRPC_STATUS_DETAILS,
566                HeaderValue::from_maybe_shared(details).map_err(invalid_header_value_byte)?,
567            );
568        }
569
570        Ok(())
571    }
572
573    /// Create a new `Status` with the associated code, message, and binary details field.
574    pub fn with_details(code: Code, message: impl Into<String>, details: Bytes) -> Status {
575        Self::with_details_and_metadata(code, message, details, MetadataMap::new())
576    }
577
578    /// Create a new `Status` with the associated code, message, and custom metadata
579    pub fn with_metadata(code: Code, message: impl Into<String>, metadata: MetadataMap) -> Status {
580        Self::with_details_and_metadata(code, message, Bytes::new(), metadata)
581    }
582
583    /// Create a new `Status` with the associated code, message, binary details field and custom metadata
584    pub fn with_details_and_metadata(
585        code: Code,
586        message: impl Into<String>,
587        details: Bytes,
588        metadata: MetadataMap,
589    ) -> Status {
590        StatusInner {
591            code,
592            message: message.into(),
593            details,
594            metadata,
595            source: None,
596        }
597        .into_status()
598    }
599
600    /// Add a source error to this status.
601    pub fn set_source(&mut self, source: Arc<dyn Error + Send + Sync + 'static>) -> &mut Status {
602        self.0.source = Some(source);
603        self
604    }
605
606    /// Build an `http::Response` from the given `Status`.
607    pub fn into_http<B: Default>(self) -> http::Response<B> {
608        let mut response = http::Response::new(B::default());
609        response
610            .headers_mut()
611            .insert(http::header::CONTENT_TYPE, GRPC_CONTENT_TYPE);
612        self.add_header(response.headers_mut()).unwrap();
613        response.extensions_mut().insert(self);
614        response
615    }
616
617    #[doc(hidden)]
618    pub const GRPC_STATUS: HeaderName = HeaderName::from_static("grpc-status");
619    #[doc(hidden)]
620    pub const GRPC_MESSAGE: HeaderName = HeaderName::from_static("grpc-message");
621    #[doc(hidden)]
622    pub const GRPC_STATUS_DETAILS: HeaderName = HeaderName::from_static("grpc-status-details-bin");
623}
624
625fn find_status_in_source_chain(err: &(dyn Error + 'static)) -> Option<Status> {
626    let mut source = Some(err);
627
628    while let Some(err) = source {
629        if let Some(status) = err.downcast_ref::<Status>() {
630            return Some(
631                StatusInner {
632                    code: status.0.code,
633                    message: status.0.message.clone(),
634                    details: status.0.details.clone(),
635                    metadata: status.0.metadata.clone(),
636                    // Since `Status` is not `Clone`, any `source` on the original Status
637                    // cannot be cloned so must remain with the original `Status`.
638                    source: None,
639                }
640                .into_status(),
641            );
642        }
643
644        if let Some(timeout) = err.downcast_ref::<TimeoutExpired>() {
645            return Some(Status::cancelled(timeout.to_string()));
646        }
647
648        // If we are unable to connect to the server, map this to UNAVAILABLE.  This is
649        // consistent with the behavior of a C++ gRPC client when the server is not running, and
650        // matches the spec of:
651        // > The service is currently unavailable. This is most likely a transient condition that
652        // > can be corrected if retried with a backoff.
653        if let Some(connect) = err.downcast_ref::<ConnectError>() {
654            return Some(Status::unavailable(connect.to_string()));
655        }
656
657        #[cfg(any(feature = "server", feature = "channel"))]
658        if let Some(hyper) = err
659            .downcast_ref::<hyper::Error>()
660            .and_then(Status::from_hyper_error)
661        {
662            return Some(hyper);
663        }
664
665        source = err.source();
666    }
667
668    None
669}
670
671impl fmt::Debug for Status {
672    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
673        self.0.fmt(f)
674    }
675}
676
677impl fmt::Debug for StatusInner {
678    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
679        // A manual impl to reduce the noise of frequently empty fields.
680        let mut builder = f.debug_struct("Status");
681
682        builder.field("code", &self.code);
683
684        if !self.message.is_empty() {
685            builder.field("message", &self.message);
686        }
687
688        if !self.details.is_empty() {
689            builder.field("details", &self.details);
690        }
691
692        if !self.metadata.is_empty() {
693            builder.field("metadata", &self.metadata);
694        }
695
696        builder.field("source", &self.source);
697
698        builder.finish()
699    }
700}
701
702fn invalid_header_value_byte<Error: fmt::Display>(err: Error) -> Status {
703    debug!("Invalid header: {}", err);
704    Status::new(
705        Code::Internal,
706        "Couldn't serialize non-text grpc status header".to_string(),
707    )
708}
709
710#[cfg(feature = "server")]
711impl From<h2::Error> for Status {
712    fn from(err: h2::Error) -> Self {
713        Status::from_h2_error(Box::new(err))
714    }
715}
716
717#[cfg(feature = "server")]
718impl From<Status> for h2::Error {
719    fn from(status: Status) -> Self {
720        status.to_h2_error()
721    }
722}
723
724impl From<std::io::Error> for Status {
725    fn from(err: std::io::Error) -> Self {
726        use std::io::ErrorKind;
727        let code = match err.kind() {
728            ErrorKind::BrokenPipe
729            | ErrorKind::WouldBlock
730            | ErrorKind::WriteZero
731            | ErrorKind::Interrupted => Code::Internal,
732            ErrorKind::ConnectionRefused
733            | ErrorKind::ConnectionReset
734            | ErrorKind::NotConnected
735            | ErrorKind::AddrInUse
736            | ErrorKind::AddrNotAvailable => Code::Unavailable,
737            ErrorKind::AlreadyExists => Code::AlreadyExists,
738            ErrorKind::ConnectionAborted => Code::Aborted,
739            ErrorKind::InvalidData => Code::DataLoss,
740            ErrorKind::InvalidInput => Code::InvalidArgument,
741            ErrorKind::NotFound => Code::NotFound,
742            ErrorKind::PermissionDenied => Code::PermissionDenied,
743            ErrorKind::TimedOut => Code::DeadlineExceeded,
744            ErrorKind::UnexpectedEof => Code::OutOfRange,
745            _ => Code::Unknown,
746        };
747        Status::new(code, err.to_string())
748    }
749}
750
751impl fmt::Display for Status {
752    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
753        write!(f, "code: '{}'", self.code())?;
754
755        if !self.message().is_empty() {
756            write!(f, ", message: {:?}", self.message())?;
757        }
758        // We intentionally omit `self.details` since it's binary data, not fit for human eyes.
759        // Additionally, `self.metadata` contains low-level details that only belong in the `Debug`
760        // impl.
761        if let Some(source) = self.source() {
762            write!(f, ", source: {source:?}")?;
763        }
764        Ok(())
765    }
766}
767
768impl Error for Status {
769    fn source(&self) -> Option<&(dyn Error + 'static)> {
770        self.0.source.as_ref().map(|err| (&**err) as _)
771    }
772}
773
774///
775/// Take the `Status` value from `trailers` if it is available, else from `status_code`.
776///
777pub(crate) fn infer_grpc_status(
778    trailers: Option<&HeaderMap>,
779    status_code: http::StatusCode,
780) -> Result<(), Option<Status>> {
781    if let Some(trailers) = trailers {
782        if let Some(status) = Status::from_header_map(trailers) {
783            if status.code() == Code::Ok {
784                return Ok(());
785            } else {
786                return Err(status.into());
787            }
788        }
789    }
790    trace!("trailers missing grpc-status");
791    let code = match status_code {
792        // Borrowed from https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md
793        http::StatusCode::BAD_REQUEST => Code::Internal,
794        http::StatusCode::UNAUTHORIZED => Code::Unauthenticated,
795        http::StatusCode::FORBIDDEN => Code::PermissionDenied,
796        http::StatusCode::NOT_FOUND => Code::Unimplemented,
797        http::StatusCode::TOO_MANY_REQUESTS
798        | http::StatusCode::BAD_GATEWAY
799        | http::StatusCode::SERVICE_UNAVAILABLE
800        | http::StatusCode::GATEWAY_TIMEOUT => Code::Unavailable,
801        // We got a 200 but no grpc-status trailer.
802        //
803        // Per the gRPC-over-HTTP/2 protocol, grpc-status MUST be present in Trailers
804        // even when the HTTP status is 200 OK. A clean end-of-stream without a
805        // grpc-status trailer is therefore a protocol violation.
806        //
807        // Tonic attempts to follow the RST_STREAM error-code mapping defined in the
808        // gRPC-over-HTTP/2 spec (see `code_from_h2` and the h2 error handling in
809        // `from_hyper_error`):
810        //   https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#errors
811        //
812        // However, hyper intentionally converts RST_STREAM frames with reason NO_ERROR
813        // or CANCEL into a clean end-of-stream (Poll::Ready(None)) instead of surfacing
814        // them as errors — see hyper's `Incoming::poll_frame` for h2 bodies. By the time
815        // tonic observes the body termination, the h2 reset reason has been discarded and
816        // is no longer accessible. Tonic therefore has no way to distinguish a legitimate
817        // graceful close from a proxy/load-balancer reset (e.g. an Envoy timeout that
818        // issues RST_STREAM(NO_ERROR)) via the h2 error path.
819        //
820        // The only signal available at this point is the absence of a grpc-status
821        // trailer on an otherwise-successful (HTTP 200) stream. We map this to Unknown,
822        // which the gRPC spec defines as the appropriate code when a final status is
823        // absent or indeterminate. This matches the behaviour of grpc-go:
824        //   https://github.com/grpc/grpc-go/pull/8702
825        //
826        // https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#responses
827        http::StatusCode::OK => {
828            return Err(Some(Status::unknown(
829                "protocol error: missing grpc-status trailer, stream was terminated without a final status (possible truncation by a proxy or load balancer)",
830            )));
831        }
832        _ => Code::Unknown,
833    };
834
835    let msg = format!(
836        "grpc-status header missing, mapped from HTTP status code {}",
837        status_code.as_u16(),
838    );
839    let status = Status::new(code, msg);
840    Err(status.into())
841}
842
843// ===== impl Code =====
844
845impl Code {
846    /// Get the `Code` that represents the integer, if known.
847    ///
848    /// If not known, returns `Code::Unknown` (surprise!).
849    pub const fn from_i32(i: i32) -> Code {
850        match i {
851            0 => Code::Ok,
852            1 => Code::Cancelled,
853            2 => Code::Unknown,
854            3 => Code::InvalidArgument,
855            4 => Code::DeadlineExceeded,
856            5 => Code::NotFound,
857            6 => Code::AlreadyExists,
858            7 => Code::PermissionDenied,
859            8 => Code::ResourceExhausted,
860            9 => Code::FailedPrecondition,
861            10 => Code::Aborted,
862            11 => Code::OutOfRange,
863            12 => Code::Unimplemented,
864            13 => Code::Internal,
865            14 => Code::Unavailable,
866            15 => Code::DataLoss,
867            16 => Code::Unauthenticated,
868
869            _ => Code::Unknown,
870        }
871    }
872
873    /// Convert the string representation of a `Code` (as stored, for example, in the `grpc-status`
874    /// header in a response) into a `Code`. Returns `Code::Unknown` if the code string is not a
875    /// valid gRPC status code.
876    pub fn from_bytes(bytes: &[u8]) -> Code {
877        match bytes.len() {
878            1 => match bytes[0] {
879                b'0' => Code::Ok,
880                b'1' => Code::Cancelled,
881                b'2' => Code::Unknown,
882                b'3' => Code::InvalidArgument,
883                b'4' => Code::DeadlineExceeded,
884                b'5' => Code::NotFound,
885                b'6' => Code::AlreadyExists,
886                b'7' => Code::PermissionDenied,
887                b'8' => Code::ResourceExhausted,
888                b'9' => Code::FailedPrecondition,
889                _ => Code::parse_err(),
890            },
891            2 => match (bytes[0], bytes[1]) {
892                (b'1', b'0') => Code::Aborted,
893                (b'1', b'1') => Code::OutOfRange,
894                (b'1', b'2') => Code::Unimplemented,
895                (b'1', b'3') => Code::Internal,
896                (b'1', b'4') => Code::Unavailable,
897                (b'1', b'5') => Code::DataLoss,
898                (b'1', b'6') => Code::Unauthenticated,
899                _ => Code::parse_err(),
900            },
901            _ => Code::parse_err(),
902        }
903    }
904
905    fn to_header_value(self) -> HeaderValue {
906        match self {
907            Code::Ok => HeaderValue::from_static("0"),
908            Code::Cancelled => HeaderValue::from_static("1"),
909            Code::Unknown => HeaderValue::from_static("2"),
910            Code::InvalidArgument => HeaderValue::from_static("3"),
911            Code::DeadlineExceeded => HeaderValue::from_static("4"),
912            Code::NotFound => HeaderValue::from_static("5"),
913            Code::AlreadyExists => HeaderValue::from_static("6"),
914            Code::PermissionDenied => HeaderValue::from_static("7"),
915            Code::ResourceExhausted => HeaderValue::from_static("8"),
916            Code::FailedPrecondition => HeaderValue::from_static("9"),
917            Code::Aborted => HeaderValue::from_static("10"),
918            Code::OutOfRange => HeaderValue::from_static("11"),
919            Code::Unimplemented => HeaderValue::from_static("12"),
920            Code::Internal => HeaderValue::from_static("13"),
921            Code::Unavailable => HeaderValue::from_static("14"),
922            Code::DataLoss => HeaderValue::from_static("15"),
923            Code::Unauthenticated => HeaderValue::from_static("16"),
924        }
925    }
926
927    fn parse_err() -> Code {
928        trace!("error parsing grpc-status");
929        Code::Unknown
930    }
931}
932
933impl From<i32> for Code {
934    fn from(i: i32) -> Self {
935        Code::from_i32(i)
936    }
937}
938
939impl From<Code> for i32 {
940    #[inline]
941    fn from(code: Code) -> i32 {
942        code as i32
943    }
944}
945
946#[cfg(test)]
947mod tests {
948    use super::*;
949    use crate::BoxError;
950
951    #[derive(Debug)]
952    struct Nested(BoxError);
953
954    impl fmt::Display for Nested {
955        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
956            write!(f, "nested error: {}", self.0)
957        }
958    }
959
960    impl std::error::Error for Nested {
961        fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
962            Some(&*self.0)
963        }
964    }
965
966    #[test]
967    fn from_error_status() {
968        let orig = Status::new(Code::OutOfRange, "weeaboo");
969        let found = Status::from_error(Box::new(orig));
970
971        assert_eq!(found.code(), Code::OutOfRange);
972        assert_eq!(found.message(), "weeaboo");
973    }
974
975    #[test]
976    fn from_error_unknown() {
977        let orig: BoxError = "peek-a-boo".into();
978        let found = Status::from_error(orig);
979
980        assert_eq!(found.code(), Code::Unknown);
981        assert_eq!(found.message(), "peek-a-boo".to_string());
982    }
983
984    #[test]
985    fn from_error_nested() {
986        let orig = Nested(Box::new(Status::new(Code::OutOfRange, "weeaboo")));
987        let found = Status::from_error(Box::new(orig));
988
989        assert_eq!(found.code(), Code::OutOfRange);
990        assert_eq!(found.message(), "weeaboo");
991    }
992
993    #[test]
994    #[cfg(feature = "server")]
995    fn from_error_h2() {
996        use std::error::Error as _;
997
998        let orig = h2::Error::from(h2::Reason::CANCEL);
999        let found = Status::from_error(Box::new(orig));
1000
1001        assert_eq!(found.code(), Code::Cancelled);
1002
1003        let source = found
1004            .source()
1005            .and_then(|err| err.downcast_ref::<h2::Error>())
1006            .unwrap();
1007        assert_eq!(source.reason(), Some(h2::Reason::CANCEL));
1008    }
1009
1010    #[test]
1011    #[cfg(feature = "server")]
1012    fn to_h2_error() {
1013        let orig = Status::new(Code::Cancelled, "stop eet!");
1014        let err = orig.to_h2_error();
1015
1016        assert_eq!(err.reason(), Some(h2::Reason::CANCEL));
1017    }
1018
1019    #[test]
1020    fn code_from_i32() {
1021        // This for loop should catch if we ever add a new variant and don't
1022        // update From<i32>.
1023        for i in 0..(Code::Unauthenticated as i32) {
1024            let code = Code::from(i);
1025            assert_eq!(
1026                i, code as i32,
1027                "Code::from({}) returned {:?} which is {}",
1028                i, code, code as i32,
1029            );
1030        }
1031
1032        assert_eq!(Code::from(-1), Code::Unknown);
1033    }
1034
1035    #[test]
1036    fn constructors() {
1037        assert_eq!(Status::ok("").code(), Code::Ok);
1038        assert_eq!(Status::cancelled("").code(), Code::Cancelled);
1039        assert_eq!(Status::unknown("").code(), Code::Unknown);
1040        assert_eq!(Status::invalid_argument("").code(), Code::InvalidArgument);
1041        assert_eq!(Status::deadline_exceeded("").code(), Code::DeadlineExceeded);
1042        assert_eq!(Status::not_found("").code(), Code::NotFound);
1043        assert_eq!(Status::already_exists("").code(), Code::AlreadyExists);
1044        assert_eq!(Status::permission_denied("").code(), Code::PermissionDenied);
1045        assert_eq!(
1046            Status::resource_exhausted("").code(),
1047            Code::ResourceExhausted
1048        );
1049        assert_eq!(
1050            Status::failed_precondition("").code(),
1051            Code::FailedPrecondition
1052        );
1053        assert_eq!(Status::aborted("").code(), Code::Aborted);
1054        assert_eq!(Status::out_of_range("").code(), Code::OutOfRange);
1055        assert_eq!(Status::unimplemented("").code(), Code::Unimplemented);
1056        assert_eq!(Status::internal("").code(), Code::Internal);
1057        assert_eq!(Status::unavailable("").code(), Code::Unavailable);
1058        assert_eq!(Status::data_loss("").code(), Code::DataLoss);
1059        assert_eq!(Status::unauthenticated("").code(), Code::Unauthenticated);
1060    }
1061
1062    #[test]
1063    fn details() {
1064        const DETAILS: &[u8] = &[0, 2, 3];
1065
1066        let status = Status::with_details(Code::Unavailable, "some message", DETAILS.into());
1067
1068        assert_eq!(status.details(), DETAILS);
1069
1070        let header_map = status.to_header_map().unwrap();
1071
1072        let b64_details = crate::util::base64::STANDARD_NO_PAD.encode(DETAILS);
1073
1074        assert_eq!(header_map[Status::GRPC_STATUS_DETAILS], b64_details);
1075
1076        let status = Status::from_header_map(&header_map).unwrap();
1077
1078        assert_eq!(status.details(), DETAILS);
1079    }
1080}
1081
1082/// Error returned if a request didn't complete within the configured timeout.
1083///
1084/// Timeouts can be configured either with [`Endpoint::timeout`], [`Server::timeout`], or by
1085/// setting the [`grpc-timeout` metadata value][spec].
1086///
1087/// [`Endpoint::timeout`]: crate::transport::channel::Endpoint::timeout
1088/// [`Server::timeout`]: crate::transport::server::Server::timeout
1089/// [spec]: https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md
1090#[derive(Debug)]
1091pub struct TimeoutExpired(pub ());
1092
1093impl fmt::Display for TimeoutExpired {
1094    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1095        write!(f, "Timeout expired")
1096    }
1097}
1098
1099// std::error::Error only requires a type to impl Debug and Display
1100impl std::error::Error for TimeoutExpired {}
1101
1102/// Wrapper type to indicate that an error occurs during the connection
1103/// process, so that the appropriate gRPC Status can be inferred.
1104#[derive(Debug)]
1105pub struct ConnectError(pub Box<dyn std::error::Error + Send + Sync>);
1106
1107impl fmt::Display for ConnectError {
1108    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1109        fmt::Display::fmt(&self.0, f)
1110    }
1111}
1112
1113impl std::error::Error for ConnectError {
1114    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1115        Some(self.0.as_ref())
1116    }
1117}