708 lines
24 KiB
Rust
708 lines
24 KiB
Rust
use bytes::Bytes;
|
|
use http::header::{HeaderMap, HeaderValue};
|
|
use percent_encoding::{percent_decode, percent_encode, AsciiSet, CONTROLS};
|
|
use std::{borrow::Cow, error::Error, fmt};
|
|
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,
|
|
}
|
|
|
|
/// 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)]
|
|
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,
|
|
|
|
// New Codes may be added in the future, so never exhaustively match!
|
|
#[doc(hidden)]
|
|
__NonExhaustive,
|
|
}
|
|
|
|
// ===== 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(),
|
|
}
|
|
}
|
|
|
|
/// 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 = "h2"), allow(dead_code))]
|
|
pub(crate) fn from_error(err: &(dyn Error + 'static)) -> Status {
|
|
Status::try_from_error(err).unwrap_or_else(|| Status::new(Code::Unknown, err.to_string()))
|
|
}
|
|
|
|
fn try_from_error(err: &(dyn Error + 'static)) -> Option<Status> {
|
|
let mut cause = Some(err);
|
|
|
|
while let Some(err) = cause {
|
|
if let Some(status) = err.downcast_ref::<Status>() {
|
|
return Some(Status {
|
|
code: status.code,
|
|
message: status.message.clone(),
|
|
details: status.details.clone(),
|
|
});
|
|
}
|
|
|
|
#[cfg(feature = "h2")]
|
|
{
|
|
if let Some(h2) = err.downcast_ref::<h2::Error>() {
|
|
return Some(Status::from_h2_error(h2));
|
|
}
|
|
}
|
|
|
|
cause = err.source();
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
// FIXME: bubble this into `transport` and expose generic http2 reasons.
|
|
#[cfg(feature = "h2")]
|
|
fn from_h2_error(err: &h2::Error) -> Status {
|
|
// See https://github.com/grpc/grpc/blob/3977c30/doc/PROTOCOL-HTTP2.md#errors
|
|
let code = 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,
|
|
};
|
|
|
|
Status::new(code, format!("h2 protocol error: {}", err))
|
|
}
|
|
|
|
#[cfg(feature = "h2")]
|
|
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()
|
|
}
|
|
|
|
pub(crate) fn map_error<E>(err: E) -> Status
|
|
where
|
|
E: Into<Box<dyn Error + Send + Sync>>,
|
|
{
|
|
Status::from_error(&*err.into())
|
|
}
|
|
|
|
pub(crate) 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| Bytes::copy_from_slice(h.as_bytes()))
|
|
.unwrap_or_else(Bytes::new);
|
|
match error_message {
|
|
Ok(message) => Status {
|
|
code,
|
|
message,
|
|
details,
|
|
},
|
|
Err(err) => {
|
|
warn!("Error deserializing status message header: {}", err);
|
|
Status {
|
|
code: Code::Unknown,
|
|
message: format!("Error deserializing status message header: {}", err),
|
|
details,
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
/// 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
|
|
}
|
|
|
|
pub(crate) fn to_header_map(&self) -> Result<HeaderMap, Self> {
|
|
let mut header_map = HeaderMap::with_capacity(3);
|
|
self.add_header(&mut header_map)?;
|
|
Ok(header_map)
|
|
}
|
|
|
|
pub(crate) fn add_header(&self, header_map: &mut HeaderMap) -> Result<(), Self> {
|
|
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() {
|
|
header_map.insert(
|
|
GRPC_STATUS_DETAILS_HEADER,
|
|
HeaderValue::from_maybe_shared(self.details.clone())
|
|
.map_err(invalid_header_value_byte)?,
|
|
);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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 = "h2")]
|
|
impl From<h2::Error> for Status {
|
|
fn from(err: h2::Error) -> Self {
|
|
Status::from_h2_error(&err)
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "h2")]
|
|
impl From<Status> for h2::Error {
|
|
fn from(status: Status) -> Self {
|
|
status.to_h2_error()
|
|
}
|
|
}
|
|
|
|
impl From<std::io::Error> for Status {
|
|
fn from(_io: std::io::Error) -> Self {
|
|
unimplemented!()
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for Status {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(
|
|
f,
|
|
"grpc-status: {:?}, grpc-message: {:?}",
|
|
self.code(),
|
|
self.message()
|
|
)
|
|
}
|
|
}
|
|
|
|
impl Error for Status {}
|
|
|
|
///
|
|
/// 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<(), 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);
|
|
}
|
|
}
|
|
}
|
|
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,
|
|
_ => 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)
|
|
}
|
|
|
|
// ===== 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)
|
|
}
|
|
|
|
pub(crate) 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"),
|
|
|
|
Code::__NonExhaustive => unreachable!("Code::__NonExhaustive"),
|
|
}
|
|
}
|
|
|
|
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,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[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(&orig);
|
|
|
|
assert_eq!(orig.code(), found.code());
|
|
assert_eq!(orig.message(), found.message());
|
|
}
|
|
|
|
#[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(), orig.to_string());
|
|
}
|
|
|
|
#[test]
|
|
fn from_error_nested() {
|
|
let orig = Nested(Box::new(Status::new(Code::OutOfRange, "weeaboo")));
|
|
let found = Status::from_error(&orig);
|
|
|
|
assert_eq!(found.code(), Code::OutOfRange);
|
|
assert_eq!(found.message(), "weeaboo");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "h2")]
|
|
fn from_error_h2() {
|
|
let orig = h2::Error::from(h2::Reason::CANCEL);
|
|
let found = Status::from_error(&orig);
|
|
|
|
assert_eq!(found.code(), Code::Cancelled);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "h2")]
|
|
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::__NonExhaustive 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);
|
|
assert_eq!(Code::from(Code::__NonExhaustive as i32), 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);
|
|
}
|
|
}
|