Skip to main content

volo_grpc/
status.rs

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