lib/momento/error.ex

defmodule Momento.Error do
  @enforce_keys [:error_code, :cause, :message]
  defstruct [:error_code, :cause, :message]

  @type t() :: %__MODULE__{
          error_code: Momento.Error.Code.t(),
          cause: Exception.t() | nil,
          message: String.t()
        }

  defimpl String.Chars, for: Momento.Error do
    def to_string(error) do
      inspect(error)
    end
  end

  @spec convert(error :: Exception.t()) :: Momento.Error.t()
  def convert(%Momento.Error{} = error), do: error
  def convert(%GRPC.RPCError{} = error), do: convert_grpc_error(error)

  def convert(%Protobuf.EncodeError{} = error),
    do: invalid_argument("Unable to encode message. Check cause for details.", error)

  def convert(error),
    do: %Momento.Error{
      error_code: Momento.Error.Code.unknown(),
      cause: error,
      message: "Momento SDK Failed to process the request."
    }

  @spec convert_grpc_error(error :: GRPC.RPCError.t()) :: Momento.Error.t()
  defp convert_grpc_error(error) do
    case error.status do
      # Cancelled
      1 ->
        %Momento.Error{
          error_code: Momento.Error.Code.cancelled_error(),
          cause: error,
          message: "The request was cancelled by the server; please contact Momento."
        }

      # Unknown
      2 ->
        %Momento.Error{
          error_code: Momento.Error.Code.unknown_service_error(),
          cause: error,
          message: "The service returned an unknown response; please contact Momento."
        }

      # InvalidArgument
      3 ->
        %Momento.Error{
          error_code: Momento.Error.Code.bad_request_error(),
          cause: error,
          message: "The request was invalid; please contact Momento."
        }

      # DeadlineExceeded
      4 ->
        %Momento.Error{
          error_code: Momento.Error.Code.timeout_error(),
          cause: error,
          message:
            "The client's configured timeout was exceeded; you may need to use a Configuration with more lenient timeouts."
        }

      # NotFound
      5 ->
        %Momento.Error{
          error_code: Momento.Error.Code.not_found_error(),
          cause: error,
          message:
            "A cache with the specified name does not exist. To resolve this error, make sure you have created the cache before attempting to use it."
        }

      # AlreadyExists
      6 ->
        %Momento.Error{
          error_code: Momento.Error.Code.already_exists_error(),
          cause: error,
          message:
            "A cache with the specified name already exists. To resolve this error, either delete the existing cache and make a new one, or use a different name."
        }

      # PermissionDenied
      7 ->
        %Momento.Error{
          error_code: Momento.Error.Code.permission_error(),
          cause: error,
          message: "Insufficient permissions to perform an operation on a cache."
        }

      # ResourceExhausted
      8 ->
        %Momento.Error{
          error_code: Momento.Error.Code.limit_exceeded_error(),
          cause: error,
          message:
            "Request rate, bandwidth, or object size exceeded the limits for this account. To resolve this error, reduce your usage as appropriate or contact Momento to request a limit increase."
        }

      # FailedPrecondition
      9 ->
        %Momento.Error{
          error_code: Momento.Error.Code.bad_request_error(),
          cause: error,
          message: "The request was invalid; please contact Momento."
        }

      # Aborted
      10 ->
        %Momento.Error{
          error_code: Momento.Error.Code.internal_server_error(),
          cause: error,
          message:
            "An unexpected error occurred while trying to fulfill the request; please contact Momento."
        }

      # OutOfRange
      11 ->
        %Momento.Error{
          error_code: Momento.Error.Code.bad_request_error(),
          cause: error,
          message: "The request was invalid; please contact Momento."
        }

      # Unimplemented
      12 ->
        %Momento.Error{
          error_code: Momento.Error.Code.bad_request_error(),
          cause: error,
          message: "The request was invalid; please contact Momento."
        }

      # Internal
      13 ->
        %Momento.Error{
          error_code: Momento.Error.Code.internal_server_error(),
          cause: error,
          message:
            "An unexpected error occurred while trying to fulfill the request; please contact Momento."
        }

      # Unavailable
      14 ->
        %Momento.Error{
          error_code: Momento.Error.Code.server_unavailable(),
          cause: error,
          message:
            "The server was unable to handle the request; consider retrying. If the error persists, please contact Momento."
        }

      # DataLoss
      15 ->
        %Momento.Error{
          error_code: Momento.Error.Code.internal_server_error(),
          cause: error,
          message:
            "An unexpected error occurred while trying to fulfill the request; please contact Momento."
        }

      # Unauthenticated
      16 ->
        %Momento.Error{
          error_code: Momento.Error.Code.authentication_error(),
          cause: error,
          message: "Invalid authentication credentials to connect to the cache service."
        }
    end
  end

  @spec invalid_argument(message :: String.t(), cause :: Exception.t() | nil) :: Momento.Error.t()
  def invalid_argument(message, cause \\ nil) do
    %Momento.Error{
      error_code: Momento.Error.Code.invalid_argument_error(),
      cause: cause,
      message: "Invalid argument passed to Momento client: #{message}"
    }
  end
end