Coverage Report

Created: 2026-07-13 08:11

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/tonic-0.14.5/src/status.rs
Line
Count
Source
1
use crate::metadata::MetadataMap;
2
use crate::metadata::GRPC_CONTENT_TYPE;
3
use base64::Engine as _;
4
use bytes::Bytes;
5
use http::{
6
    header::{HeaderMap, HeaderValue},
7
    HeaderName,
8
};
9
use percent_encoding::{percent_decode, percent_encode, AsciiSet, CONTROLS};
10
use std::{borrow::Cow, error::Error, fmt, sync::Arc};
11
use tracing::{debug, trace, warn};
12
13
const 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)]
38
pub struct Status(Box<StatusInner>);
39
40
/// Box the contents of Status to avoid large error variants
41
#[derive(Clone)]
42
struct 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
57
impl StatusInner {
58
0
    fn into_status(self) -> Status {
59
0
        Status(Box::new(self))
60
0
    }
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)]
69
pub 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
122
impl 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
0
    pub fn description(&self) -> &'static str {
136
0
        match self {
137
0
            Code::Ok => "The operation completed successfully",
138
0
            Code::Cancelled => "The operation was cancelled",
139
0
            Code::Unknown => "Unknown error",
140
0
            Code::InvalidArgument => "Client specified an invalid argument",
141
0
            Code::DeadlineExceeded => "Deadline expired before operation could complete",
142
0
            Code::NotFound => "Some requested entity was not found",
143
0
            Code::AlreadyExists => "Some entity that we attempted to create already exists",
144
            Code::PermissionDenied => {
145
0
                "The caller does not have permission to execute the specified operation"
146
            }
147
0
            Code::ResourceExhausted => "Some resource has been exhausted",
148
            Code::FailedPrecondition => {
149
0
                "The system is not in a state required for the operation's execution"
150
            }
151
0
            Code::Aborted => "The operation was aborted",
152
0
            Code::OutOfRange => "Operation was attempted past the valid range",
153
0
            Code::Unimplemented => "Operation is not implemented or not supported",
154
0
            Code::Internal => "Internal error",
155
0
            Code::Unavailable => "The service is currently unavailable",
156
0
            Code::DataLoss => "Unrecoverable data loss or corruption",
157
0
            Code::Unauthenticated => "The request does not have valid authentication credentials",
158
        }
159
0
    }
160
}
161
162
impl std::fmt::Display for Code {
163
0
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164
0
        std::fmt::Display::fmt(self.description(), f)
165
0
    }
166
}
167
168
// ===== impl Status =====
169
170
impl Status {
171
    /// Create a new `Status` with the associated code and message.
172
0
    pub fn new(code: Code, message: impl Into<String>) -> Status {
173
0
        StatusInner {
174
0
            code,
175
0
            message: message.into(),
176
0
            details: Bytes::new(),
177
0
            metadata: MetadataMap::new(),
178
0
            source: None,
179
0
        }
180
0
        .into_status()
181
0
    }
Unexecuted instantiation: <tonic::status::Status>::new::<alloc::string::String>
Unexecuted instantiation: <tonic::status::Status>::new::<&str>
182
183
    /// The operation completed successfully.
184
0
    pub fn ok(message: impl Into<String>) -> Status {
185
0
        Status::new(Code::Ok, message)
186
0
    }
187
188
    /// The operation was cancelled (typically by the caller).
189
0
    pub fn cancelled(message: impl Into<String>) -> Status {
190
0
        Status::new(Code::Cancelled, message)
191
0
    }
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
0
    pub fn unknown(message: impl Into<String>) -> Status {
198
0
        Status::new(Code::Unknown, message)
199
0
    }
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
0
    pub fn invalid_argument(message: impl Into<String>) -> Status {
206
0
        Status::new(Code::InvalidArgument, message)
207
0
    }
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
0
    pub fn deadline_exceeded(message: impl Into<String>) -> Status {
215
0
        Status::new(Code::DeadlineExceeded, message)
216
0
    }
217
218
    /// Some requested entity (e.g., file or directory) was not found.
219
0
    pub fn not_found(message: impl Into<String>) -> Status {
220
0
        Status::new(Code::NotFound, message)
221
0
    }
222
223
    /// Some entity that we attempted to create (e.g., file or directory) already
224
    /// exists.
225
0
    pub fn already_exists(message: impl Into<String>) -> Status {
226
0
        Status::new(Code::AlreadyExists, message)
227
0
    }
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
0
    pub fn permission_denied(message: impl Into<String>) -> Status {
235
0
        Status::new(Code::PermissionDenied, message)
236
0
    }
237
238
    /// Some resource has been exhausted, perhaps a per-user quota, or perhaps
239
    /// the entire file system is out of space.
240
0
    pub fn resource_exhausted(message: impl Into<String>) -> Status {
241
0
        Status::new(Code::ResourceExhausted, message)
242
0
    }
Unexecuted instantiation: <tonic::status::Status>::resource_exhausted::<alloc::string::String>
Unexecuted instantiation: <tonic::status::Status>::resource_exhausted::<&str>
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
0
    pub fn failed_precondition(message: impl Into<String>) -> Status {
259
0
        Status::new(Code::FailedPrecondition, message)
260
0
    }
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
0
    pub fn aborted(message: impl Into<String>) -> Status {
268
0
        Status::new(Code::Aborted, message)
269
0
    }
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
0
    pub fn out_of_range(message: impl Into<String>) -> Status {
285
0
        Status::new(Code::OutOfRange, message)
286
0
    }
287
288
    /// Operation is not implemented or not supported/enabled in this service.
289
0
    pub fn unimplemented(message: impl Into<String>) -> Status {
290
0
        Status::new(Code::Unimplemented, message)
291
0
    }
Unexecuted instantiation: <tonic::status::Status>::unimplemented::<alloc::string::String>
Unexecuted instantiation: <tonic::status::Status>::unimplemented::<&str>
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
0
    pub fn internal(message: impl Into<String>) -> Status {
296
0
        Status::new(Code::Internal, message)
297
0
    }
Unexecuted instantiation: <tonic::status::Status>::internal::<alloc::string::String>
Unexecuted instantiation: <tonic::status::Status>::internal::<&str>
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
0
    pub fn unavailable(message: impl Into<String>) -> Status {
305
0
        Status::new(Code::Unavailable, message)
306
0
    }
307
308
    /// Unrecoverable data loss or corruption.
309
0
    pub fn data_loss(message: impl Into<String>) -> Status {
310
0
        Status::new(Code::DataLoss, message)
311
0
    }
312
313
    /// The request does not have valid authentication credentials for the
314
    /// operation.
315
0
    pub fn unauthenticated(message: impl Into<String>) -> Status {
316
0
        Status::new(Code::Unauthenticated, message)
317
0
    }
318
319
0
    pub(crate) fn from_error_generic(
320
0
        err: impl Into<Box<dyn Error + Send + Sync + 'static>>,
321
0
    ) -> Status {
322
0
        Self::from_error(err.into())
323
0
    }
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
0
    pub fn from_error(err: Box<dyn Error + Send + Sync + 'static>) -> Status {
330
0
        Status::try_from_error(err).unwrap_or_else(|err| {
331
0
            let mut status = Status::new(Code::Unknown, err.to_string());
332
0
            status.0.source = Some(err.into());
333
0
            status
334
0
        })
335
0
    }
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
0
    pub fn try_from_error(
345
0
        err: Box<dyn Error + Send + Sync + 'static>,
346
0
    ) -> Result<Status, Box<dyn Error + Send + Sync + 'static>> {
347
0
        let err = match err.downcast::<Status>() {
348
0
            Ok(status) => {
349
0
                return Ok(*status);
350
            }
351
0
            Err(err) => err,
352
        };
353
354
        #[cfg(feature = "server")]
355
0
        let err = match err.downcast::<h2::Error>() {
356
0
            Ok(h2) => {
357
0
                return Ok(Status::from_h2_error(h2));
358
            }
359
0
            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
0
        let err = match err.downcast::<tower::load_shed::error::Overloaded>() {
366
            Ok(_) => {
367
0
                return Ok(Status::resource_exhausted(
368
0
                    "Too many active requests for the connection",
369
0
                ));
370
            }
371
0
            Err(err) => err,
372
        };
373
374
0
        if let Some(mut status) = find_status_in_source_chain(&*err) {
375
0
            status.0.source = Some(err.into());
376
0
            return Ok(status);
377
0
        }
378
379
0
        Err(err)
380
0
    }
381
382
    // FIXME: bubble this into `transport` and expose generic http2 reasons.
383
    #[cfg(feature = "server")]
384
0
    fn from_h2_error(err: Box<h2::Error>) -> Status {
385
0
        let code = Self::code_from_h2(&err);
386
387
0
        let mut status = Self::new(code, format!("h2 protocol error: {err}"));
388
0
        status.0.source = Some(Arc::new(*err));
389
0
        status
390
0
    }
391
392
    #[cfg(feature = "server")]
393
0
    fn code_from_h2(err: &h2::Error) -> Code {
394
        // See https://github.com/grpc/grpc/blob/3977c30/doc/PROTOCOL-HTTP2.md#errors
395
0
        match err.reason() {
396
            Some(h2::Reason::NO_ERROR)
397
            | Some(h2::Reason::PROTOCOL_ERROR)
398
            | Some(h2::Reason::INTERNAL_ERROR)
399
            | Some(h2::Reason::FLOW_CONTROL_ERROR)
400
            | Some(h2::Reason::SETTINGS_TIMEOUT)
401
            | Some(h2::Reason::COMPRESSION_ERROR)
402
0
            | Some(h2::Reason::CONNECT_ERROR) => Code::Internal,
403
0
            Some(h2::Reason::REFUSED_STREAM) => Code::Unavailable,
404
0
            Some(h2::Reason::CANCEL) => Code::Cancelled,
405
0
            Some(h2::Reason::ENHANCE_YOUR_CALM) => Code::ResourceExhausted,
406
0
            Some(h2::Reason::INADEQUATE_SECURITY) => Code::PermissionDenied,
407
408
0
            _ => Code::Unknown,
409
        }
410
0
    }
411
412
    #[cfg(feature = "server")]
413
0
    fn to_h2_error(&self) -> h2::Error {
414
        // conservatively transform to h2 error codes...
415
0
        let reason = match self.code() {
416
0
            Code::Cancelled => h2::Reason::CANCEL,
417
0
            _ => h2::Reason::INTERNAL_ERROR,
418
        };
419
420
0
        reason.into()
421
0
    }
422
423
    /// Handles hyper errors specifically, which expose a number of different parameters about the
424
    /// http stream's error: https://docs.rs/hyper/0.14.11/hyper/struct.Error.html.
425
    ///
426
    /// Returns Some if there's a way to handle the error, or None if the information from this
427
    /// hyper error, but perhaps not its source, should be ignored.
428
    #[cfg(any(feature = "server", feature = "channel"))]
429
0
    fn from_hyper_error(err: &hyper::Error) -> Option<Status> {
430
        // is_timeout results from hyper's keep-alive logic
431
        // (https://docs.rs/hyper/0.14.11/src/hyper/error.rs.html#192-194).  Per the grpc spec
432
        // > An expired client initiated PING will cause all calls to be closed with an UNAVAILABLE
433
        // > status. Note that the frequency of PINGs is highly dependent on the network
434
        // > environment, implementations are free to adjust PING frequency based on network and
435
        // > application requirements, which is why it's mapped to unavailable here.
436
0
        if err.is_timeout() {
437
0
            return Some(Status::unavailable(err.to_string()));
438
0
        }
439
440
0
        if err.is_canceled() {
441
0
            return Some(Status::cancelled(err.to_string()));
442
0
        }
443
444
        #[cfg(feature = "server")]
445
0
        if let Some(h2_err) = err.source().and_then(|e| e.downcast_ref::<h2::Error>()) {
446
0
            let code = Status::code_from_h2(h2_err);
447
0
            let status = Self::new(code, format!("h2 protocol error: {err}"));
448
449
0
            return Some(status);
450
0
        }
451
452
0
        None
453
0
    }
454
455
0
    pub(crate) fn map_error<E>(err: E) -> Status
456
0
    where
457
0
        E: Into<Box<dyn Error + Send + Sync>>,
458
    {
459
0
        let err: Box<dyn Error + Send + Sync> = err.into();
460
0
        Status::from_error(err)
461
0
    }
Unexecuted instantiation: <tonic::status::Status>::map_error::<hyper::error::Error>
Unexecuted instantiation: <tonic::status::Status>::map_error::<axum_core::error::Error>
462
463
    /// Extract a `Status` from a hyper `HeaderMap`.
464
0
    pub fn from_header_map(header_map: &HeaderMap) -> Option<Status> {
465
0
        let code = Code::from_bytes(header_map.get(Self::GRPC_STATUS)?.as_ref());
466
467
0
        let error_message = match header_map.get(Self::GRPC_MESSAGE) {
468
0
            Some(header) => percent_decode(header.as_bytes())
469
0
                .decode_utf8()
470
0
                .map(|cow| cow.to_string()),
471
0
            None => Ok(String::new()),
472
        };
473
474
0
        let details = match header_map.get(Self::GRPC_STATUS_DETAILS) {
475
0
            Some(header) => crate::util::base64::STANDARD
476
0
                .decode(header.as_bytes())
477
0
                .expect("Invalid status header, expected base64 encoded value")
478
0
                .into(),
479
0
            None => Bytes::new(),
480
        };
481
482
0
        let other_headers = {
483
0
            let mut header_map = header_map.clone();
484
0
            header_map.remove(Self::GRPC_STATUS);
485
0
            header_map.remove(Self::GRPC_MESSAGE);
486
0
            header_map.remove(Self::GRPC_STATUS_DETAILS);
487
0
            header_map
488
        };
489
490
0
        let (code, message) = match error_message {
491
0
            Ok(message) => (code, message),
492
0
            Err(e) => {
493
0
                let error_message = format!("Error deserializing status message header: {e}");
494
0
                warn!(error_message);
495
0
                (Code::Unknown, error_message)
496
            }
497
        };
498
499
0
        Some(
500
0
            StatusInner {
501
0
                code,
502
0
                message,
503
0
                details,
504
0
                metadata: MetadataMap::from_headers(other_headers),
505
0
                source: None,
506
0
            }
507
0
            .into_status(),
508
0
        )
509
0
    }
510
511
    /// Get the gRPC `Code` of this `Status`.
512
0
    pub fn code(&self) -> Code {
513
0
        self.0.code
514
0
    }
515
516
    /// Get the text error message of this `Status`.
517
0
    pub fn message(&self) -> &str {
518
0
        &self.0.message
519
0
    }
520
521
    /// Get the opaque error details of this `Status`.
522
0
    pub fn details(&self) -> &[u8] {
523
0
        &self.0.details
524
0
    }
525
526
    /// Get a reference to the custom metadata.
527
0
    pub fn metadata(&self) -> &MetadataMap {
528
0
        &self.0.metadata
529
0
    }
530
531
    /// Get a mutable reference to the custom metadata.
532
0
    pub fn metadata_mut(&mut self) -> &mut MetadataMap {
533
0
        &mut self.0.metadata
534
0
    }
535
536
0
    pub(crate) fn to_header_map(&self) -> Result<HeaderMap, Self> {
537
0
        let mut header_map = HeaderMap::with_capacity(3 + self.0.metadata.len());
538
0
        self.add_header(&mut header_map)?;
539
0
        Ok(header_map)
540
0
    }
541
542
    /// Add headers from this `Status` into `header_map`.
543
0
    pub fn add_header(&self, header_map: &mut HeaderMap) -> Result<(), Self> {
544
0
        header_map.extend(self.0.metadata.clone().into_sanitized_headers());
545
546
0
        header_map.insert(Self::GRPC_STATUS, self.0.code.to_header_value());
547
548
0
        if !self.0.message.is_empty() {
549
0
            let to_write = Bytes::copy_from_slice(
550
0
                Cow::from(percent_encode(self.message().as_bytes(), ENCODING_SET)).as_bytes(),
551
            );
552
553
0
            header_map.insert(
554
0
                Self::GRPC_MESSAGE,
555
0
                HeaderValue::from_maybe_shared(to_write).map_err(invalid_header_value_byte)?,
556
            );
557
0
        }
558
559
0
        if !self.0.details.is_empty() {
560
0
            let details = crate::util::base64::STANDARD_NO_PAD.encode(&self.0.details[..]);
561
562
0
            header_map.insert(
563
0
                Self::GRPC_STATUS_DETAILS,
564
0
                HeaderValue::from_maybe_shared(details).map_err(invalid_header_value_byte)?,
565
            );
566
0
        }
567
568
0
        Ok(())
569
0
    }
570
571
    /// Create a new `Status` with the associated code, message, and binary details field.
572
0
    pub fn with_details(code: Code, message: impl Into<String>, details: Bytes) -> Status {
573
0
        Self::with_details_and_metadata(code, message, details, MetadataMap::new())
574
0
    }
575
576
    /// Create a new `Status` with the associated code, message, and custom metadata
577
0
    pub fn with_metadata(code: Code, message: impl Into<String>, metadata: MetadataMap) -> Status {
578
0
        Self::with_details_and_metadata(code, message, Bytes::new(), metadata)
579
0
    }
580
581
    /// Create a new `Status` with the associated code, message, binary details field and custom metadata
582
0
    pub fn with_details_and_metadata(
583
0
        code: Code,
584
0
        message: impl Into<String>,
585
0
        details: Bytes,
586
0
        metadata: MetadataMap,
587
0
    ) -> Status {
588
0
        StatusInner {
589
0
            code,
590
0
            message: message.into(),
591
0
            details,
592
0
            metadata,
593
0
            source: None,
594
0
        }
595
0
        .into_status()
596
0
    }
597
598
    /// Add a source error to this status.
599
0
    pub fn set_source(&mut self, source: Arc<dyn Error + Send + Sync + 'static>) -> &mut Status {
600
0
        self.0.source = Some(source);
601
0
        self
602
0
    }
603
604
    /// Build an `http::Response` from the given `Status`.
605
0
    pub fn into_http<B: Default>(self) -> http::Response<B> {
606
0
        let mut response = http::Response::new(B::default());
607
0
        response
608
0
            .headers_mut()
609
0
            .insert(http::header::CONTENT_TYPE, GRPC_CONTENT_TYPE);
610
0
        self.add_header(response.headers_mut()).unwrap();
611
0
        response.extensions_mut().insert(self);
612
0
        response
613
0
    }
614
615
    #[doc(hidden)]
616
    pub const GRPC_STATUS: HeaderName = HeaderName::from_static("grpc-status");
617
    #[doc(hidden)]
618
    pub const GRPC_MESSAGE: HeaderName = HeaderName::from_static("grpc-message");
619
    #[doc(hidden)]
620
    pub const GRPC_STATUS_DETAILS: HeaderName = HeaderName::from_static("grpc-status-details-bin");
621
}
622
623
0
fn find_status_in_source_chain(err: &(dyn Error + 'static)) -> Option<Status> {
624
0
    let mut source = Some(err);
625
626
0
    while let Some(err) = source {
627
0
        if let Some(status) = err.downcast_ref::<Status>() {
628
0
            return Some(
629
0
                StatusInner {
630
0
                    code: status.0.code,
631
0
                    message: status.0.message.clone(),
632
0
                    details: status.0.details.clone(),
633
0
                    metadata: status.0.metadata.clone(),
634
0
                    // Since `Status` is not `Clone`, any `source` on the original Status
635
0
                    // cannot be cloned so must remain with the original `Status`.
636
0
                    source: None,
637
0
                }
638
0
                .into_status(),
639
0
            );
640
0
        }
641
642
0
        if let Some(timeout) = err.downcast_ref::<TimeoutExpired>() {
643
0
            return Some(Status::cancelled(timeout.to_string()));
644
0
        }
645
646
        // If we are unable to connect to the server, map this to UNAVAILABLE.  This is
647
        // consistent with the behavior of a C++ gRPC client when the server is not running, and
648
        // matches the spec of:
649
        // > The service is currently unavailable. This is most likely a transient condition that
650
        // > can be corrected if retried with a backoff.
651
0
        if let Some(connect) = err.downcast_ref::<ConnectError>() {
652
0
            return Some(Status::unavailable(connect.to_string()));
653
0
        }
654
655
        #[cfg(any(feature = "server", feature = "channel"))]
656
0
        if let Some(hyper) = err
657
0
            .downcast_ref::<hyper::Error>()
658
0
            .and_then(Status::from_hyper_error)
659
        {
660
0
            return Some(hyper);
661
0
        }
662
663
0
        source = err.source();
664
    }
665
666
0
    None
667
0
}
668
669
impl fmt::Debug for Status {
670
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
671
0
        self.0.fmt(f)
672
0
    }
673
}
674
675
impl fmt::Debug for StatusInner {
676
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
677
        // A manual impl to reduce the noise of frequently empty fields.
678
0
        let mut builder = f.debug_struct("Status");
679
680
0
        builder.field("code", &self.code);
681
682
0
        if !self.message.is_empty() {
683
0
            builder.field("message", &self.message);
684
0
        }
685
686
0
        if !self.details.is_empty() {
687
0
            builder.field("details", &self.details);
688
0
        }
689
690
0
        if !self.metadata.is_empty() {
691
0
            builder.field("metadata", &self.metadata);
692
0
        }
693
694
0
        builder.field("source", &self.source);
695
696
0
        builder.finish()
697
0
    }
698
}
699
700
0
fn invalid_header_value_byte<Error: fmt::Display>(err: Error) -> Status {
701
0
    debug!("Invalid header: {}", err);
702
0
    Status::new(
703
0
        Code::Internal,
704
0
        "Couldn't serialize non-text grpc status header".to_string(),
705
    )
706
0
}
707
708
#[cfg(feature = "server")]
709
impl From<h2::Error> for Status {
710
0
    fn from(err: h2::Error) -> Self {
711
0
        Status::from_h2_error(Box::new(err))
712
0
    }
713
}
714
715
#[cfg(feature = "server")]
716
impl From<Status> for h2::Error {
717
0
    fn from(status: Status) -> Self {
718
0
        status.to_h2_error()
719
0
    }
720
}
721
722
impl From<std::io::Error> for Status {
723
0
    fn from(err: std::io::Error) -> Self {
724
        use std::io::ErrorKind;
725
0
        let code = match err.kind() {
726
            ErrorKind::BrokenPipe
727
            | ErrorKind::WouldBlock
728
            | ErrorKind::WriteZero
729
0
            | ErrorKind::Interrupted => Code::Internal,
730
            ErrorKind::ConnectionRefused
731
            | ErrorKind::ConnectionReset
732
            | ErrorKind::NotConnected
733
            | ErrorKind::AddrInUse
734
0
            | ErrorKind::AddrNotAvailable => Code::Unavailable,
735
0
            ErrorKind::AlreadyExists => Code::AlreadyExists,
736
0
            ErrorKind::ConnectionAborted => Code::Aborted,
737
0
            ErrorKind::InvalidData => Code::DataLoss,
738
0
            ErrorKind::InvalidInput => Code::InvalidArgument,
739
0
            ErrorKind::NotFound => Code::NotFound,
740
0
            ErrorKind::PermissionDenied => Code::PermissionDenied,
741
0
            ErrorKind::TimedOut => Code::DeadlineExceeded,
742
0
            ErrorKind::UnexpectedEof => Code::OutOfRange,
743
0
            _ => Code::Unknown,
744
        };
745
0
        Status::new(code, err.to_string())
746
0
    }
747
}
748
749
impl fmt::Display for Status {
750
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
751
0
        write!(f, "code: '{}'", self.code())?;
752
753
0
        if !self.message().is_empty() {
754
0
            write!(f, ", message: {:?}", self.message())?;
755
0
        }
756
        // We intentionally omit `self.details` since it's binary data, not fit for human eyes.
757
        // Additionally, `self.metadata` contains low-level details that only belong in the `Debug`
758
        // impl.
759
0
        if let Some(source) = self.source() {
760
0
            write!(f, ", source: {source:?}")?;
761
0
        }
762
0
        Ok(())
763
0
    }
764
}
765
766
impl Error for Status {
767
0
    fn source(&self) -> Option<&(dyn Error + 'static)> {
768
0
        self.0.source.as_ref().map(|err| (&**err) as _)
769
0
    }
770
}
771
772
///
773
/// Take the `Status` value from `trailers` if it is available, else from `status_code`.
774
///
775
0
pub(crate) fn infer_grpc_status(
776
0
    trailers: Option<&HeaderMap>,
777
0
    status_code: http::StatusCode,
778
0
) -> Result<(), Option<Status>> {
779
0
    if let Some(trailers) = trailers {
780
0
        if let Some(status) = Status::from_header_map(trailers) {
781
0
            if status.code() == Code::Ok {
782
0
                return Ok(());
783
            } else {
784
0
                return Err(status.into());
785
            }
786
0
        }
787
0
    }
788
0
    trace!("trailers missing grpc-status");
789
0
    let code = match status_code {
790
        // Borrowed from https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md
791
0
        http::StatusCode::BAD_REQUEST => Code::Internal,
792
0
        http::StatusCode::UNAUTHORIZED => Code::Unauthenticated,
793
0
        http::StatusCode::FORBIDDEN => Code::PermissionDenied,
794
0
        http::StatusCode::NOT_FOUND => Code::Unimplemented,
795
        http::StatusCode::TOO_MANY_REQUESTS
796
        | http::StatusCode::BAD_GATEWAY
797
        | http::StatusCode::SERVICE_UNAVAILABLE
798
0
        | http::StatusCode::GATEWAY_TIMEOUT => Code::Unavailable,
799
        // We got a 200 but no trailers, we can infer that this request is finished.
800
        //
801
        // This can happen when a streaming response sends two Status but
802
        // gRPC requires that we end the stream after the first status.
803
        //
804
        // https://github.com/hyperium/tonic/issues/681
805
0
        http::StatusCode::OK => return Err(None),
806
0
        _ => Code::Unknown,
807
    };
808
809
0
    let msg = format!(
810
        "grpc-status header missing, mapped from HTTP status code {}",
811
0
        status_code.as_u16(),
812
    );
813
0
    let status = Status::new(code, msg);
814
0
    Err(status.into())
815
0
}
816
817
// ===== impl Code =====
818
819
impl Code {
820
    /// Get the `Code` that represents the integer, if known.
821
    ///
822
    /// If not known, returns `Code::Unknown` (surprise!).
823
0
    pub const fn from_i32(i: i32) -> Code {
824
0
        match i {
825
0
            0 => Code::Ok,
826
0
            1 => Code::Cancelled,
827
0
            2 => Code::Unknown,
828
0
            3 => Code::InvalidArgument,
829
0
            4 => Code::DeadlineExceeded,
830
0
            5 => Code::NotFound,
831
0
            6 => Code::AlreadyExists,
832
0
            7 => Code::PermissionDenied,
833
0
            8 => Code::ResourceExhausted,
834
0
            9 => Code::FailedPrecondition,
835
0
            10 => Code::Aborted,
836
0
            11 => Code::OutOfRange,
837
0
            12 => Code::Unimplemented,
838
0
            13 => Code::Internal,
839
0
            14 => Code::Unavailable,
840
0
            15 => Code::DataLoss,
841
0
            16 => Code::Unauthenticated,
842
843
0
            _ => Code::Unknown,
844
        }
845
0
    }
846
847
    /// Convert the string representation of a `Code` (as stored, for example, in the `grpc-status`
848
    /// header in a response) into a `Code`. Returns `Code::Unknown` if the code string is not a
849
    /// valid gRPC status code.
850
0
    pub fn from_bytes(bytes: &[u8]) -> Code {
851
0
        match bytes.len() {
852
0
            1 => match bytes[0] {
853
0
                b'0' => Code::Ok,
854
0
                b'1' => Code::Cancelled,
855
0
                b'2' => Code::Unknown,
856
0
                b'3' => Code::InvalidArgument,
857
0
                b'4' => Code::DeadlineExceeded,
858
0
                b'5' => Code::NotFound,
859
0
                b'6' => Code::AlreadyExists,
860
0
                b'7' => Code::PermissionDenied,
861
0
                b'8' => Code::ResourceExhausted,
862
0
                b'9' => Code::FailedPrecondition,
863
0
                _ => Code::parse_err(),
864
            },
865
0
            2 => match (bytes[0], bytes[1]) {
866
0
                (b'1', b'0') => Code::Aborted,
867
0
                (b'1', b'1') => Code::OutOfRange,
868
0
                (b'1', b'2') => Code::Unimplemented,
869
0
                (b'1', b'3') => Code::Internal,
870
0
                (b'1', b'4') => Code::Unavailable,
871
0
                (b'1', b'5') => Code::DataLoss,
872
0
                (b'1', b'6') => Code::Unauthenticated,
873
0
                _ => Code::parse_err(),
874
            },
875
0
            _ => Code::parse_err(),
876
        }
877
0
    }
878
879
0
    fn to_header_value(self) -> HeaderValue {
880
0
        match self {
881
0
            Code::Ok => HeaderValue::from_static("0"),
882
0
            Code::Cancelled => HeaderValue::from_static("1"),
883
0
            Code::Unknown => HeaderValue::from_static("2"),
884
0
            Code::InvalidArgument => HeaderValue::from_static("3"),
885
0
            Code::DeadlineExceeded => HeaderValue::from_static("4"),
886
0
            Code::NotFound => HeaderValue::from_static("5"),
887
0
            Code::AlreadyExists => HeaderValue::from_static("6"),
888
0
            Code::PermissionDenied => HeaderValue::from_static("7"),
889
0
            Code::ResourceExhausted => HeaderValue::from_static("8"),
890
0
            Code::FailedPrecondition => HeaderValue::from_static("9"),
891
0
            Code::Aborted => HeaderValue::from_static("10"),
892
0
            Code::OutOfRange => HeaderValue::from_static("11"),
893
0
            Code::Unimplemented => HeaderValue::from_static("12"),
894
0
            Code::Internal => HeaderValue::from_static("13"),
895
0
            Code::Unavailable => HeaderValue::from_static("14"),
896
0
            Code::DataLoss => HeaderValue::from_static("15"),
897
0
            Code::Unauthenticated => HeaderValue::from_static("16"),
898
        }
899
0
    }
900
901
0
    fn parse_err() -> Code {
902
0
        trace!("error parsing grpc-status");
903
0
        Code::Unknown
904
0
    }
905
}
906
907
impl From<i32> for Code {
908
0
    fn from(i: i32) -> Self {
909
0
        Code::from_i32(i)
910
0
    }
911
}
912
913
impl From<Code> for i32 {
914
    #[inline]
915
0
    fn from(code: Code) -> i32 {
916
0
        code as i32
917
0
    }
918
}
919
920
#[cfg(test)]
921
mod tests {
922
    use super::*;
923
    use crate::BoxError;
924
925
    #[derive(Debug)]
926
    struct Nested(BoxError);
927
928
    impl fmt::Display for Nested {
929
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
930
            write!(f, "nested error: {}", self.0)
931
        }
932
    }
933
934
    impl std::error::Error for Nested {
935
        fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
936
            Some(&*self.0)
937
        }
938
    }
939
940
    #[test]
941
    fn from_error_status() {
942
        let orig = 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_unknown() {
951
        let orig: BoxError = "peek-a-boo".into();
952
        let found = Status::from_error(orig);
953
954
        assert_eq!(found.code(), Code::Unknown);
955
        assert_eq!(found.message(), "peek-a-boo".to_string());
956
    }
957
958
    #[test]
959
    fn from_error_nested() {
960
        let orig = Nested(Box::new(Status::new(Code::OutOfRange, "weeaboo")));
961
        let found = Status::from_error(Box::new(orig));
962
963
        assert_eq!(found.code(), Code::OutOfRange);
964
        assert_eq!(found.message(), "weeaboo");
965
    }
966
967
    #[test]
968
    #[cfg(feature = "server")]
969
    fn from_error_h2() {
970
        use std::error::Error as _;
971
972
        let orig = h2::Error::from(h2::Reason::CANCEL);
973
        let found = Status::from_error(Box::new(orig));
974
975
        assert_eq!(found.code(), Code::Cancelled);
976
977
        let source = found
978
            .source()
979
            .and_then(|err| err.downcast_ref::<h2::Error>())
980
            .unwrap();
981
        assert_eq!(source.reason(), Some(h2::Reason::CANCEL));
982
    }
983
984
    #[test]
985
    #[cfg(feature = "server")]
986
    fn to_h2_error() {
987
        let orig = Status::new(Code::Cancelled, "stop eet!");
988
        let err = orig.to_h2_error();
989
990
        assert_eq!(err.reason(), Some(h2::Reason::CANCEL));
991
    }
992
993
    #[test]
994
    fn code_from_i32() {
995
        // This for loop should catch if we ever add a new variant and don't
996
        // update From<i32>.
997
        for i in 0..(Code::Unauthenticated as i32) {
998
            let code = Code::from(i);
999
            assert_eq!(
1000
                i, code as i32,
1001
                "Code::from({}) returned {:?} which is {}",
1002
                i, code, code as i32,
1003
            );
1004
        }
1005
1006
        assert_eq!(Code::from(-1), Code::Unknown);
1007
    }
1008
1009
    #[test]
1010
    fn constructors() {
1011
        assert_eq!(Status::ok("").code(), Code::Ok);
1012
        assert_eq!(Status::cancelled("").code(), Code::Cancelled);
1013
        assert_eq!(Status::unknown("").code(), Code::Unknown);
1014
        assert_eq!(Status::invalid_argument("").code(), Code::InvalidArgument);
1015
        assert_eq!(Status::deadline_exceeded("").code(), Code::DeadlineExceeded);
1016
        assert_eq!(Status::not_found("").code(), Code::NotFound);
1017
        assert_eq!(Status::already_exists("").code(), Code::AlreadyExists);
1018
        assert_eq!(Status::permission_denied("").code(), Code::PermissionDenied);
1019
        assert_eq!(
1020
            Status::resource_exhausted("").code(),
1021
            Code::ResourceExhausted
1022
        );
1023
        assert_eq!(
1024
            Status::failed_precondition("").code(),
1025
            Code::FailedPrecondition
1026
        );
1027
        assert_eq!(Status::aborted("").code(), Code::Aborted);
1028
        assert_eq!(Status::out_of_range("").code(), Code::OutOfRange);
1029
        assert_eq!(Status::unimplemented("").code(), Code::Unimplemented);
1030
        assert_eq!(Status::internal("").code(), Code::Internal);
1031
        assert_eq!(Status::unavailable("").code(), Code::Unavailable);
1032
        assert_eq!(Status::data_loss("").code(), Code::DataLoss);
1033
        assert_eq!(Status::unauthenticated("").code(), Code::Unauthenticated);
1034
    }
1035
1036
    #[test]
1037
    fn details() {
1038
        const DETAILS: &[u8] = &[0, 2, 3];
1039
1040
        let status = Status::with_details(Code::Unavailable, "some message", DETAILS.into());
1041
1042
        assert_eq!(status.details(), DETAILS);
1043
1044
        let header_map = status.to_header_map().unwrap();
1045
1046
        let b64_details = crate::util::base64::STANDARD_NO_PAD.encode(DETAILS);
1047
1048
        assert_eq!(header_map[Status::GRPC_STATUS_DETAILS], b64_details);
1049
1050
        let status = Status::from_header_map(&header_map).unwrap();
1051
1052
        assert_eq!(status.details(), DETAILS);
1053
    }
1054
}
1055
1056
/// Error returned if a request didn't complete within the configured timeout.
1057
///
1058
/// Timeouts can be configured either with [`Endpoint::timeout`], [`Server::timeout`], or by
1059
/// setting the [`grpc-timeout` metadata value][spec].
1060
///
1061
/// [`Endpoint::timeout`]: crate::transport::channel::Endpoint::timeout
1062
/// [`Server::timeout`]: crate::transport::server::Server::timeout
1063
/// [spec]: https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md
1064
#[derive(Debug)]
1065
pub struct TimeoutExpired(pub ());
1066
1067
impl fmt::Display for TimeoutExpired {
1068
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1069
0
        write!(f, "Timeout expired")
1070
0
    }
1071
}
1072
1073
// std::error::Error only requires a type to impl Debug and Display
1074
impl std::error::Error for TimeoutExpired {}
1075
1076
/// Wrapper type to indicate that an error occurs during the connection
1077
/// process, so that the appropriate gRPC Status can be inferred.
1078
#[derive(Debug)]
1079
pub struct ConnectError(pub Box<dyn std::error::Error + Send + Sync>);
1080
1081
impl fmt::Display for ConnectError {
1082
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1083
0
        fmt::Display::fmt(&self.0, f)
1084
0
    }
1085
}
1086
1087
impl std::error::Error for ConnectError {
1088
0
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1089
0
        Some(self.0.as_ref())
1090
0
    }
1091
}