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 ->
create_error(
Momento.Error.Code.cancelled_error(),
error,
"The request was cancelled by the server; please contact Momento."
)
# Unknown
2 ->
create_error(
Momento.Error.Code.unknown_service_error(),
error,
"The service returned an unknown response; please contact Momento."
)
# InvalidArgument
3 ->
create_error(
Momento.Error.Code.invalid_argument_error(),
error,
"Invalid argument passed to Momento client."
)
# DeadlineExceeded
4 ->
create_error(
Momento.Error.Code.timeout_error(),
error,
"The client's configured timeout was exceeded; you may need to use a Configuration with more lenient timeouts."
)
# NotFound
5 ->
create_error(
Momento.Error.Code.not_found_error(),
error,
"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 ->
create_error(
Momento.Error.Code.already_exists_error(),
error,
"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 ->
create_error(
Momento.Error.Code.permission_error(),
error,
"Insufficient permissions to perform an operation on a cache."
)
# ResourceExhausted
8 ->
handle_limit_exceeded_error(error)
# FailedPrecondition
9 ->
create_error(
Momento.Error.Code.failed_precondition(),
error,
"System is not in a state required for the operation's execution."
)
# Aborted
10 ->
create_error(
Momento.Error.Code.internal_server_error(),
error,
"An unexpected error occurred while trying to fulfill the request; please contact Momento."
)
# OutOfRange
11 ->
create_error(
Momento.Error.Code.bad_request_error(),
error,
"The request was invalid; please contact Momento."
)
# Unimplemented
12 ->
create_error(
Momento.Error.Code.bad_request_error(),
error,
"The request was invalid; please contact Momento."
)
# Internal
13 ->
create_error(
Momento.Error.Code.internal_server_error(),
error,
"An unexpected error occurred while trying to fulfill the request; please contact Momento."
)
# Unavailable
14 ->
create_error(
Momento.Error.Code.server_unavailable(),
error,
"The server was unable to handle the request; consider retrying. If the error persists, please contact Momento."
)
# DataLoss
15 ->
create_error(
Momento.Error.Code.internal_server_error(),
error,
"An unexpected error occurred while trying to fulfill the request; please contact Momento."
)
# Unauthenticated
16 ->
create_error(
Momento.Error.Code.authentication_error(),
error,
"Invalid authentication credentials to connect to the cache service."
)
end
end
defmodule LimitExceededMessages do
@messages %{
"topic_subscriptions_limit_exceeded" => "Topic subscriptions limit exceeded.",
"operations_rate_limit_exceeded" => "Operations rate limit exceeded.",
"throughput_rate_limit_exceeded" => "Throughput rate limit exceeded.",
"request_size_limit_exceeded" => "Request size limit exceeded.",
"item_size_limit_exceeded" => "Item size limit exceeded.",
"element_size_limit_exceeded" => "Element size limit exceeded."
}
@default_message "Limit exceeded for this account."
def determine_limit_exceeded_message(error_cause) do
Map.get(@messages, error_cause, default_limit_exceeded_message(error_cause))
end
defp default_limit_exceeded_message(error_cause) do
cond do
String.contains?(String.downcase(error_cause), "subscribers") ->
@messages["topic_subscriptions_limit_exceeded"]
String.contains?(String.downcase(error_cause), "operations") ->
@messages["operations_rate_limit_exceeded"]
String.contains?(String.downcase(error_cause), "throughput") ->
@messages["throughput_rate_limit_exceeded"]
String.contains?(String.downcase(error_cause), "request limit") ->
@messages["request_size_limit_exceeded"]
String.contains?(String.downcase(error_cause), "item size") ->
@messages["item_size_limit_exceeded"]
String.contains?(String.downcase(error_cause), "element size") ->
@messages["element_size_limit_exceeded"]
true ->
@default_message
end
end
end
defp handle_limit_exceeded_error(error) do
message = LimitExceededMessages.determine_limit_exceeded_message(error.metadata["err"] || "")
%Momento.Error{
error_code: Momento.Error.Code.limit_exceeded_error(),
cause: error,
message: message
}
end
defp create_error(error_code, cause, message) do
%Momento.Error{
error_code: error_code,
cause: cause,
message: message
}
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