| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362 |
- /*
- * Copyright 2019, gRPC Authors All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- import NIOCore
- import NIOHTTP1
- import NIOHTTP2
- /// Encapsulates the result of a gRPC call.
- public struct GRPCStatus: Error, Sendable {
- /// Storage for message/cause. In the happy case ('ok') there will not be a message or cause
- /// and this will reference a static storage containing nil values. Making it optional makes the
- /// setters for message and cause a little messy.
- private var storage: Storage
- /// The status code of the RPC.
- public var code: Code
- /// The status message of the RPC.
- public var message: String? {
- get {
- return self.storage.message
- }
- set {
- if isKnownUniquelyReferenced(&self.storage) {
- self.storage.message = newValue
- } else {
- self.storage = .makeStorage(message: newValue, cause: self.storage.cause)
- }
- }
- }
- /// The cause of an error (not 'ok') status. This value is never transmitted over the wire and is
- /// **not** included in equality checks.
- public var cause: Error? {
- get {
- return self.storage.cause
- }
- set {
- if isKnownUniquelyReferenced(&self.storage) {
- self.storage.cause = newValue
- } else {
- self.storage = .makeStorage(message: self.storage.message, cause: newValue)
- }
- }
- }
- // Backing storage for 'message' and 'cause'.
- fileprivate final class Storage {
- // On many happy paths there will be no message or cause, so we'll use this shared reference
- // instead of allocating a new storage each time.
- //
- // Alternatively: `GRPCStatus` could hold a storage optionally however doing so made the code
- // quite unreadable.
- private static let none = Storage(message: nil, cause: nil)
- private init(message: String?, cause: Error?) {
- self.message = message
- self.cause = cause
- }
- fileprivate var message: Optional<String>
- fileprivate var cause: Optional<Error>
- fileprivate static func makeStorage(message: String?, cause: Error?) -> Storage {
- if message == nil, cause == nil {
- return Storage.none
- } else {
- return Storage(message: message, cause: cause)
- }
- }
- }
- /// Whether the status is '.ok'.
- public var isOk: Bool {
- return self.code == .ok
- }
- public init(code: Code, message: String?) {
- self.init(code: code, message: message, cause: nil)
- }
- public init(code: Code, message: String? = nil, cause: Error? = nil) {
- self.code = code
- self.storage = .makeStorage(message: message, cause: cause)
- }
- // Frequently used "default" statuses.
- /// The default status to return for succeeded calls.
- ///
- /// - Important: This should *not* be used when checking whether a returned status has an 'ok'
- /// status code. Use `GRPCStatus.isOk` or check the code directly.
- public static let ok = GRPCStatus(code: .ok, message: nil)
- /// "Internal server error" status.
- public static let processingError = Self.processingError(cause: nil)
- public static func processingError(cause: Error?) -> GRPCStatus {
- return GRPCStatus(
- code: .internalError,
- message: "unknown error processing request",
- cause: cause
- )
- }
- }
- extension GRPCStatus: Equatable {
- public static func == (lhs: GRPCStatus, rhs: GRPCStatus) -> Bool {
- return lhs.code == rhs.code && lhs.message == rhs.message
- }
- }
- extension GRPCStatus: CustomStringConvertible {
- public var description: String {
- switch (self.message, self.cause) {
- case let (.some(message), .some(cause)):
- return "\(self.code): \(message), cause: \(cause)"
- case let (.some(message), .none):
- return "\(self.code): \(message)"
- case let (.none, .some(cause)):
- return "\(self.code), cause: \(cause)"
- case (.none, .none):
- return "\(self.code)"
- }
- }
- }
- extension GRPCStatus {
- internal var testingOnly_storageObjectIdentifier: ObjectIdentifier {
- return ObjectIdentifier(self.storage)
- }
- }
- extension GRPCStatus {
- /// Status codes for gRPC operations (replicated from `status_code_enum.h` in the
- /// [gRPC core library](https://github.com/grpc/grpc)).
- public struct Code: Hashable, CustomStringConvertible, Sendable {
- // `rawValue` must be an `Int` for API reasons and we don't need (or want) to store anything so
- // wide, a `UInt8` is fine.
- private let _rawValue: UInt8
- public var rawValue: Int {
- return Int(self._rawValue)
- }
- public init?(rawValue: Int) {
- switch rawValue {
- case 0 ... 16:
- self._rawValue = UInt8(truncatingIfNeeded: rawValue)
- default:
- return nil
- }
- }
- private init(_ code: UInt8) {
- self._rawValue = code
- }
- /// Not an error; returned on success.
- public static let ok = Code(0)
- /// The operation was cancelled (typically by the caller).
- public static let cancelled = Code(1)
- /// 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.
- public static let unknown = Code(2)
- /// Client specified an invalid argument. Note that this differs from
- /// FAILED_PRECONDITION. INVALID_ARGUMENT indicates arguments that are
- /// problematic regardless of the state of the system (e.g., a malformed file
- /// name).
- public static let invalidArgument = Code(3)
- /// 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.
- public static let deadlineExceeded = Code(4)
- /// Some requested entity (e.g., file or directory) was not found.
- public static let notFound = Code(5)
- /// Some entity that we attempted to create (e.g., file or directory) already
- /// exists.
- public static let alreadyExists = Code(6)
- /// The caller does not have permission to execute the specified operation.
- /// PERMISSION_DENIED must not be used for rejections caused by exhausting
- /// some resource (use RESOURCE_EXHAUSTED instead for those errors).
- /// PERMISSION_DENIED must not be used if the caller can not be identified
- /// (use UNAUTHENTICATED instead for those errors).
- public static let permissionDenied = Code(7)
- /// Some resource has been exhausted, perhaps a per-user quota, or perhaps the
- /// entire file system is out of space.
- public static let resourceExhausted = Code(8)
- /// 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 FAILED_PRECONDITION, 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 FAILED_PRECONDITION 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, FAILED_PRECONDITION
- /// should be returned since the client should not retry unless
- /// they have first fixed up the directory by deleting files from it.
- /// (d) Use FAILED_PRECONDITION if the client performs conditional
- /// REST Get/Update/Delete on a resource and the resource on the
- /// server does not match the condition. E.g., conflicting
- /// read-modify-write on the same resource.
- public static let failedPrecondition = Code(9)
- /// The operation was aborted, typically due to a concurrency issue like
- /// sequencer check failures, transaction aborts, etc.
- ///
- /// See litmus test above for deciding between FAILED_PRECONDITION, ABORTED,
- /// and UNAVAILABLE.
- public static let aborted = Code(10)
- /// Operation was attempted past the valid range. E.g., seeking or reading
- /// past end of file.
- ///
- /// Unlike INVALID_ARGUMENT, this error indicates a problem that may be fixed
- /// if the system state changes. For example, a 32-bit file system will
- /// generate INVALID_ARGUMENT if asked to read at an offset that is not in the
- /// range [0,2^32-1], but it will generate OUT_OF_RANGE if asked to read from
- /// an offset past the current file size.
- ///
- /// There is a fair bit of overlap between FAILED_PRECONDITION and
- /// OUT_OF_RANGE. We recommend using OUT_OF_RANGE (the more specific error)
- /// when it applies so that callers who are iterating through a space can
- /// easily look for an OUT_OF_RANGE error to detect when they are done.
- public static let outOfRange = Code(11)
- /// Operation is not implemented or not supported/enabled in this service.
- public static let unimplemented = Code(12)
- /// Internal errors. Means some invariants expected by underlying System has
- /// been broken. If you see one of these errors, Something is very broken.
- public static let internalError = Code(13)
- /// The service is currently unavailable. This is a most likely a transient
- /// condition and may be corrected by retrying with a backoff.
- ///
- /// See litmus test above for deciding between FAILED_PRECONDITION, ABORTED,
- /// and UNAVAILABLE.
- public static let unavailable = Code(14)
- /// Unrecoverable data loss or corruption.
- public static let dataLoss = Code(15)
- /// The request does not have valid authentication credentials for the
- /// operation.
- public static let unauthenticated = Code(16)
- public var description: String {
- switch self {
- case .ok:
- return "ok (\(self._rawValue))"
- case .cancelled:
- return "cancelled (\(self._rawValue))"
- case .unknown:
- return "unknown (\(self._rawValue))"
- case .invalidArgument:
- return "invalid argument (\(self._rawValue))"
- case .deadlineExceeded:
- return "deadline exceeded (\(self._rawValue))"
- case .notFound:
- return "not found (\(self._rawValue))"
- case .alreadyExists:
- return "already exists (\(self._rawValue))"
- case .permissionDenied:
- return "permission denied (\(self._rawValue))"
- case .resourceExhausted:
- return "resource exhausted (\(self._rawValue))"
- case .failedPrecondition:
- return "failed precondition (\(self._rawValue))"
- case .aborted:
- return "aborted (\(self._rawValue))"
- case .outOfRange:
- return "out of range (\(self._rawValue))"
- case .unimplemented:
- return "unimplemented (\(self._rawValue))"
- case .internalError:
- return "internal error (\(self._rawValue))"
- case .unavailable:
- return "unavailable (\(self._rawValue))"
- case .dataLoss:
- return "data loss (\(self._rawValue))"
- case .unauthenticated:
- return "unauthenticated (\(self._rawValue))"
- default:
- return String(describing: self._rawValue)
- }
- }
- }
- }
- // `GRPCStatus` has CoW semantics so it is inherently `Sendable`. Rather than marking `GRPCStatus`
- // as `@unchecked Sendable` we only mark `Storage` as such.
- extension GRPCStatus.Storage: @unchecked Sendable {}
- /// This protocol serves as a customisation point for error types so that gRPC calls may be
- /// terminated with an appropriate status.
- public protocol GRPCStatusTransformable: Error {
- /// Make a `GRPCStatus` from the underlying error.
- ///
- /// - Returns: A `GRPCStatus` representing the underlying error.
- func makeGRPCStatus() -> GRPCStatus
- }
- extension GRPCStatus: GRPCStatusTransformable {
- public func makeGRPCStatus() -> GRPCStatus {
- return self
- }
- }
- extension NIOHTTP2Errors.StreamClosed: GRPCStatusTransformable {
- public func makeGRPCStatus() -> GRPCStatus {
- return .init(code: .unavailable, message: self.localizedDescription, cause: self)
- }
- }
- extension NIOHTTP2Errors.IOOnClosedConnection: GRPCStatusTransformable {
- public func makeGRPCStatus() -> GRPCStatus {
- return .init(code: .unavailable, message: "The connection is closed", cause: self)
- }
- }
- extension ChannelError: GRPCStatusTransformable {
- public func makeGRPCStatus() -> GRPCStatus {
- switch self {
- case .inputClosed, .outputClosed, .ioOnClosedChannel:
- return .init(code: .unavailable, message: "The connection is closed", cause: self)
- default:
- var processingError = GRPCStatus.processingError
- processingError.cause = self
- return processingError
- }
- }
- }
|