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