diff --git a/tonic/src/status.rs b/tonic/src/status.rs index aaa7b0f79..17ad3bf5f 100644 --- a/tonic/src/status.rs +++ b/tonic/src/status.rs @@ -9,6 +9,17 @@ 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. @@ -25,24 +36,56 @@ pub struct Status { /// /// [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)] -#[allow(missing_docs)] 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! @@ -62,13 +105,140 @@ impl Status { } } - /// Create a new `Unimplemented` status with the associated message. + /// The operation completed successfully. + pub fn ok(message: impl Into) -> Status { + Status::new(Code::Ok, message) + } + + /// The operation was cancelled (typically by the caller). + pub fn cancelled(message: impl Into) -> 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) -> 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) -> 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) -> Status { + Status::new(Code::DeadlineExceeded, message) + } + + /// Some requested entity (e.g., file or directory) was not found. + pub fn not_found(message: impl Into) -> 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) -> 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) -> 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) -> 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) -> 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) -> 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) -> Status { + Status::new(Code::OutOfRange, message) + } + + /// Operation is not implemented or not supported/enabled in this service. pub fn unimplemented(message: impl Into) -> Status { - Status { - code: Code::Unimplemented, - message: message.into(), - details: Bytes::new(), - } + 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) -> 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) -> Status { + Status::new(Code::Unavailable, message) + } + + /// Unrecoverable data loss or corruption. + pub fn data_loss(message: impl Into) -> Status { + Status::new(Code::DataLoss, message) + } + + /// The request does not have valid authentication credentials for the + /// operation. + pub fn unauthenticated(message: impl Into) -> Status { + Status::new(Code::Unauthenticated, message) } #[cfg_attr(not(feature = "h2"), allow(dead_code))] @@ -505,4 +675,31 @@ mod tests { 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); + } }