grpc-spec-1.0.0: src/Network/GRPC/Spec/Status.hs
module Network.GRPC.Spec.Status (
-- * GRPC status
GrpcStatus(..)
, GrpcError(..)
, fromGrpcStatus
, fromGrpcError
, toGrpcStatus
, toGrpcError
-- * Exceptions
, GrpcException(..)
, throwGrpcError
-- * Details
, Status
) where
import Control.Exception
import Data.ByteString qualified as Strict (ByteString)
import Data.List (intercalate)
import Data.Text (Text)
import Data.Text qualified as Text
import GHC.Generics (Generic)
import Network.GRPC.Spec.CustomMetadata.Raw (CustomMetadata)
import Proto.Status
{-------------------------------------------------------------------------------
gRPC status
-------------------------------------------------------------------------------}
-- | gRPC status
--
-- Defined in <https://github.com/grpc/grpc/blob/master/doc/statuscodes.md>.
data GrpcStatus =
GrpcOk
| GrpcError GrpcError
deriving stock (Show, Eq, Generic)
-- | gRPC error code
--
-- This is a subset of the gRPC status codes. See 'GrpcStatus'.
data GrpcError =
-- | Cancelled
--
-- The operation was cancelled, typically by the caller.
GrpcCancelled
-- | Unknown error
--
-- For example, this error may be returned when 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.
| GrpcUnknown
-- | Invalid argument
--
-- The client specified an invalid argument. Note that this differs from
-- 'GrpcFailedPrecondition': 'GrpcInvalidArgument' indicates arguments that
-- are problematic regardless of the state of the system (e.g., a malformed
-- file name).
| GrpcInvalidArgument
-- | Deadline exceeded
--
-- The deadline expired before the 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.
| GrpcDeadlineExceeded
-- | Not found
--
-- Some requested entity (e.g., file or directory) was not found.
--
-- Note to server developers: if a request is denied for an entire class of
-- users, such as gradual feature rollout or undocumented allowlist,
-- 'GrpcNotFound' may be used.
--
-- If a request is denied for some users within a class of users, such as
-- user-based access control, 'GrpcPermissionDenied' must be used.
| GrpcNotFound
-- | Already exists
--
-- The entity that a client attempted to create (e.g., file or directory)
-- already exists.
| GrpcAlreadyExists
-- | Permission denied
--
-- The caller does not have permission to execute the specified operation.
--
-- * 'GrpcPermissionDenied' must not be used for rejections caused by
-- exhausting some resource (use 'GrpcResourceExhausted' instead for those
-- errors).
-- * 'GrpcPermissionDenied' must not be used if the caller can not be
-- identified (use 'GrpcUnauthenticated' instead for those errors).
--
-- This error code does not imply the request is valid or the requested
-- entity exists or satisfies other pre-conditions.
| GrpcPermissionDenied
-- | Resource exhausted
--
-- Some resource has been exhausted, perhaps a per-user quota, or perhaps
-- the entire file system is out of space.
| GrpcResourceExhausted
-- | Failed precondition
--
-- The operation was rejected because the system is not in a state required
-- for the operation's execution. For example, the directory to be deleted
-- is non-empty, an rmdir operation is applied to a non-directory, etc.
--
-- Service implementors can use the following guidelines to decide between
-- 'GrpcFailedPrecondition', 'GrpcAborted', and 'GrpcUnavailable':
--
-- (a) Use 'GrpcUnavailable' if the client can retry just the failing call.
-- (b) Use 'GrpcAborted' if the client should retry at a higher level (e.g.,
-- when a client-specified test-and-set fails, indicating the client
-- should restart a read-modify-write sequence).
-- (c) Use `GrpcFailedPrecondition` 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, 'GrpcFailedPrecondition' should
-- be returned since the client should not retry unless the files are
-- deleted from the directory.
| GrpcFailedPrecondition
-- | Aborted
--
-- The operation was aborted, typically due to a concurrency issue such as a
-- sequencer check failure or transaction abort. See the guidelines above
-- for deciding between 'GrpcFailedPrecondition', 'GrpcAborted', and
-- 'GrpcUnavailable'.
| GrpcAborted
-- | Out of range
--
-- The operation was attempted past the valid range. E.g., seeking or
-- reading past end-of-file.
--
-- Unlike 'GrpcInvalidArgument', this error indicates a problem that may be
-- fixed if the system state changes. For example, a 32-bit file system will
-- generate 'GrpcInvalidArgument' if asked to read at an offset that is not
-- in the range @[0, 2^32-1]@, but it will generate 'GrpcOutOfRange' if
-- asked to read from an offset past the current file size.
--
-- There is a fair bit of overlap between 'GrpcFailedPrecondition' and
-- 'GrpcOutOfRange'. We recommend using 'GrpcOutOfRange' (the more specific
-- error) when it applies so that callers who are iterating through a space
-- can easily look for an 'GrpcOutOfRange' error to detect when they are
-- done.
| GrpcOutOfRange
-- | Unimplemented
--
-- The operation is not implemented or is not supported/enabled in this
-- service.
| GrpcUnimplemented
-- | Internal errors
--
-- This means that some invariants expected by the underlying system have
-- been broken. This error code is reserved for serious errors.
| GrpcInternal
-- | Unavailable
--
-- The service is currently unavailable. This is most likely a transient
-- condition, which can be corrected by retrying with a backoff. Note that
-- it is not always safe to retry non-idempotent operations.
| GrpcUnavailable
-- | Data loss
--
-- Unrecoverable data loss or corruption.
| GrpcDataLoss
-- | Unauthenticated
--
-- The request does not have valid authentication credentials for the
-- operation.
| GrpcUnauthenticated
deriving stock (Show, Eq, Ord, Generic)
deriving anyclass (Exception)
{-------------------------------------------------------------------------------
Status codes
-------------------------------------------------------------------------------}
-- | Translate 'GrpcStatus' to numerical status code
--
-- See <https://grpc.github.io/grpc/core/md_doc_statuscodes.html>
fromGrpcStatus :: GrpcStatus -> Word
fromGrpcStatus GrpcOk = 0
fromGrpcStatus (GrpcError err) = fromGrpcError err
-- | Translate 'GrpcError' to numerical status code
--
-- See also 'fromGrpcStatus'
fromGrpcError :: GrpcError -> Word
fromGrpcError GrpcCancelled = 1
fromGrpcError GrpcUnknown = 2
fromGrpcError GrpcInvalidArgument = 3
fromGrpcError GrpcDeadlineExceeded = 4
fromGrpcError GrpcNotFound = 5
fromGrpcError GrpcAlreadyExists = 6
fromGrpcError GrpcPermissionDenied = 7
fromGrpcError GrpcResourceExhausted = 8
fromGrpcError GrpcFailedPrecondition = 9
fromGrpcError GrpcAborted = 10
fromGrpcError GrpcOutOfRange = 11
fromGrpcError GrpcUnimplemented = 12
fromGrpcError GrpcInternal = 13
fromGrpcError GrpcUnavailable = 14
fromGrpcError GrpcDataLoss = 15
fromGrpcError GrpcUnauthenticated = 16
-- | Inverse to 'fromGrpcStatus'
toGrpcStatus :: Word -> Maybe GrpcStatus
toGrpcStatus 0 = Just $ GrpcOk
toGrpcStatus s = GrpcError <$> toGrpcError s
-- | Inverse to 'fromGrpcError'
toGrpcError :: Word -> Maybe GrpcError
toGrpcError 1 = Just $ GrpcCancelled
toGrpcError 2 = Just $ GrpcUnknown
toGrpcError 3 = Just $ GrpcInvalidArgument
toGrpcError 4 = Just $ GrpcDeadlineExceeded
toGrpcError 5 = Just $ GrpcNotFound
toGrpcError 6 = Just $ GrpcAlreadyExists
toGrpcError 7 = Just $ GrpcPermissionDenied
toGrpcError 8 = Just $ GrpcResourceExhausted
toGrpcError 9 = Just $ GrpcFailedPrecondition
toGrpcError 10 = Just $ GrpcAborted
toGrpcError 11 = Just $ GrpcOutOfRange
toGrpcError 12 = Just $ GrpcUnimplemented
toGrpcError 13 = Just $ GrpcInternal
toGrpcError 14 = Just $ GrpcUnavailable
toGrpcError 15 = Just $ GrpcDataLoss
toGrpcError 16 = Just $ GrpcUnauthenticated
toGrpcError _ = Nothing
{-------------------------------------------------------------------------------
gRPC exceptions
-------------------------------------------------------------------------------}
-- | Server indicated a gRPC error
--
-- For the common case where you just want to set 'grpcError', you can use
-- 'throwGrpcError'.
data GrpcException = GrpcException {
grpcError :: GrpcError
, grpcErrorMessage :: Maybe Text
, grpcErrorDetails :: Maybe Strict.ByteString
, grpcErrorMetadata :: [CustomMetadata]
}
deriving stock (Show, Eq)
instance Exception GrpcException where
displayException GrpcException{
grpcError
, grpcErrorMessage
, grpcErrorDetails
, grpcErrorMetadata
} = (intercalate "\n" . concat) [
[ concat [
"gRPC exception "
, show grpcError
, " ("
, show (fromGrpcError grpcError)
, ")"
]
]
, [ intercalate "\n" $
"Error message:"
: (map ("| " ++) . lines $ Text.unpack msg)
| Just msg <- [grpcErrorMessage]
]
, [ "Additional details are available (see 'grpcErrorDetails')."
| Just _details <- [grpcErrorDetails]
]
, [ show md
| md <- grpcErrorMetadata
]
]
-- | Convenience function to throw an t'GrpcException' with the specified error
throwGrpcError :: GrpcError -> IO a
throwGrpcError grpcError = throwIO $ GrpcException {
grpcError
, grpcErrorMessage = Nothing
, grpcErrorDetails = Nothing
, grpcErrorMetadata = []
}