Skip to main content

tonic/
status.rs

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