Current section
Files
Jump to
Current section
Files
lib/redis_pool/error.ex
defmodule RedisPool.Error do
@moduledoc """
定义 RedisPool 库中使用的错误类型和错误处理函数。
本模块提供了统一的错误类型定义和错误处理机制,使得整个库中的错误处理更加一致和可预测。
通过使用统一的错误结构,可以更容易地区分不同类型的错误,并提供更详细的错误信息。
错误类型包括:
- `:connection_error` - 连接相关错误
- `:command_error` - 命令执行错误
- `:timeout_error` - 超时错误
- `:network_error` - 网络错误
- `:authentication_error` - 认证错误
- `:pool_error` - 连接池错误
- `:unknown_error` - 未知错误
此模块同时提供了错误处理辅助函数,用于转换和规范化错误。
"""
@typedoc """
基本错误类型,包含错误码、错误消息和原因
## 字段
- `code`: 错误类型代码,用于分类错误
- `message`: 人类可读的错误消息
- `reason`: 底层错误原因,可以是任何类型
"""
@type t :: %__MODULE__{
code: error_code_t(),
message: String.t(),
reason: term()
}
@typedoc """
错误码类型,用于标识不同类型的错误
## 值
- `:connection_error` - 与 Redis 连接相关的错误
- `:command_error` - Redis 命令执行错误
- `:timeout_error` - 操作超时错误
- `:network_error` - 网络通信错误
- `:authentication_error` - Redis 认证失败错误
- `:pool_error` - 连接池管理错误
- `:unknown_error` - 未分类的未知错误
"""
@type error_code_t ::
:connection_error
| :command_error
| :timeout_error
| :network_error
| :authentication_error
| :pool_error
| :unknown_error
defexception [:code, :message, :reason]
@doc """
实现 Exception 协议的 message/1 函数,返回格式化的错误消息。
此函数将错误的代码、消息和原因组合成一个格式化的字符串,便于日志记录和错误报告。
## 参数
- `error`: RedisPool.Error 结构体
## 返回值
返回格式化的错误消息字符串
## 示例
iex> error = %RedisPool.Error{code: :connection_error, message: "连接失败", reason: :timeout}
iex> RedisPool.Error.message(error)
"RedisPool错误 [connection_error]: 连接失败, 原因: :timeout"
"""
@spec message(t()) :: String.t()
def message(%__MODULE__{code: code, message: message, reason: reason}) do
"RedisPool错误 [#{code}]: #{message}, 原因: #{inspect(reason)}"
end
@doc """
创建连接错误。
用于表示与 Redis 服务器建立或维护连接相关的错误。
这可能包括无法连接到服务器、连接断开或连接超时等情况。
## 参数
- `message`: 错误消息,描述连接错误的性质
- `reason`: 错误原因,可以是任何与错误相关的额外信息,默认为 `nil`
## 返回值
返回一个格式化的 RedisPool.Error 结构体
## 示例
iex> RedisPool.Error.connection_error("无法连接到 Redis 服务器", :econnrefused)
%RedisPool.Error{code: :connection_error, message: "无法连接到 Redis 服务器", reason: :econnrefused}
"""
@spec connection_error(String.t(), term()) :: t()
def connection_error(message, reason \\ nil) do
%__MODULE__{
code: :connection_error,
message: message,
reason: reason
}
end
@doc """
创建命令执行错误。
用于表示 Redis 命令执行过程中发生的错误。
这可能包括无效的命令语法、参数错误或 Redis 服务器返回的错误响应。
## 参数
- `message`: 错误消息,描述命令错误的性质
- `reason`: 错误原因,可以是 Redis 服务器返回的错误信息或其他相关数据,默认为 `nil`
## 返回值
返回一个格式化的 RedisPool.Error 结构体
## 示例
iex> RedisPool.Error.command_error("HSET 命令参数不足", "ERR wrong number of arguments for 'hset' command")
%RedisPool.Error{code: :command_error, message: "HSET 命令参数不足", reason: "ERR wrong number of arguments for 'hset' command"}
"""
@spec command_error(String.t(), term()) :: t()
def command_error(message, reason \\ nil) do
%__MODULE__{
code: :command_error,
message: message,
reason: reason
}
end
@doc """
创建超时错误。
用于表示操作超时的情况。这可能发生在连接池获取连接超时、
命令执行超时或网络通信超时等场景。
## 参数
- `message`: 错误消息,描述超时错误的性质
- `reason`: 错误原因,可以包含超时时间或其他相关信息,默认为 `nil`
## 返回值
返回一个格式化的 RedisPool.Error 结构体
## 示例
iex> RedisPool.Error.timeout_error("执行 BLPOP 命令超时", {:timeout, 5000})
%RedisPool.Error{code: :timeout_error, message: "执行 BLPOP 命令超时", reason: {:timeout, 5000}}
"""
@spec timeout_error(String.t(), term()) :: t()
def timeout_error(message, reason \\ nil) do
%__MODULE__{
code: :timeout_error,
message: message,
reason: reason
}
end
@doc """
创建网络错误。
用于表示网络通信中发生的错误,如连接关闭、网络不可达、
主机不可达等网络层面的问题。
## 参数
- `message`: 错误消息,描述网络错误的性质
- `reason`: 错误原因,可以是底层网络错误代码或其他相关信息,默认为 `nil`
## 返回值
返回一个格式化的 RedisPool.Error 结构体
## 示例
iex> RedisPool.Error.network_error("Redis 连接意外关闭", :closed)
%RedisPool.Error{code: :network_error, message: "Redis 连接意外关闭", reason: :closed}
iex> RedisPool.Error.network_error("网络不可达", :enetunreach)
%RedisPool.Error{code: :network_error, message: "网络不可达", reason: :enetunreach}
"""
@spec network_error(String.t(), term()) :: t()
def network_error(message, reason \\ nil) do
%__MODULE__{
code: :network_error,
message: message,
reason: reason
}
end
@doc """
创建认证错误。
用于表示 Redis 认证失败的情况,通常是由于提供了错误的密码
或者认证机制不匹配导致的。
## 参数
- `message`: 错误消息,描述认证错误的性质
- `reason`: 错误原因,可以是 Redis 服务器返回的认证错误信息,默认为 `nil`
## 返回值
返回一个格式化的 RedisPool.Error 结构体
## 示例
iex> RedisPool.Error.authentication_error("Redis 认证失败", "ERR invalid password")
%RedisPool.Error{code: :authentication_error, message: "Redis 认证失败", reason: "ERR invalid password"}
"""
@spec authentication_error(String.t(), term()) :: t()
def authentication_error(message, reason \\ nil) do
%__MODULE__{
code: :authentication_error,
message: message,
reason: reason
}
end
@doc """
创建连接池错误。
用于表示连接池管理中发生的错误,如无法创建足够的连接、
连接池已满、连接池配置错误等情况。
## 参数
- `message`: 错误消息,描述连接池错误的性质
- `reason`: 错误原因,可以是连接池相关的错误信息,默认为 `nil`
## 返回值
返回一个格式化的 RedisPool.Error 结构体
## 示例
iex> RedisPool.Error.pool_error("连接池已满", {:pool_full, 10})
%RedisPool.Error{code: :pool_error, message: "连接池已满", reason: {:pool_full, 10}}
"""
@spec pool_error(String.t(), term()) :: t()
def pool_error(message, reason \\ nil) do
%__MODULE__{
code: :pool_error,
message: message,
reason: reason
}
end
@doc """
创建未知错误。
用于表示无法归类到其他特定错误类型的情况。
这是一个通用的错误类型,通常用作最后的错误处理手段。
## 参数
- `message`: 错误消息,描述未知错误的性质
- `reason`: 错误原因,可以是任何与错误相关的信息,默认为 `nil`
## 返回值
返回一个格式化的 RedisPool.Error 结构体
## 示例
iex> RedisPool.Error.unknown_error("意外的错误情况", {:strange_error, "未知原因"})
%RedisPool.Error{code: :unknown_error, message: "意外的错误情况", reason: {:strange_error, "未知原因"}}
"""
@spec unknown_error(String.t(), term()) :: t()
def unknown_error(message, reason \\ nil) do
%__MODULE__{
code: :unknown_error,
message: message,
reason: reason
}
end
@doc """
将 Redix 错误转换为 RedisPool 错误。
此函数用于将 Redix 库返回的错误转换为 RedisPool 的标准错误格式,
确保整个应用程序中的错误处理一致性。
## 参数
- `error`: Redix 错误,通常是 `{:error, reason}` 格式
## 返回值
返回对应的 RedisPool.Error 结构体
## 示例
iex> RedisPool.Error.from_redix_error({:error, %Redix.ConnectionError{reason: :closed}})
%RedisPool.Error{code: :connection_error, message: "Redis连接错误", reason: :closed}
iex> RedisPool.Error.from_redix_error({:error, %Redix.Error{message: "WRONGTYPE"}})
%RedisPool.Error{code: :command_error, message: "Redis命令错误", reason: "WRONGTYPE"}
iex> RedisPool.Error.from_redix_error({:error, :timeout})
%RedisPool.Error{code: :timeout_error, message: "Redis操作超时", reason: :timeout}
"""
@spec from_redix_error(term()) :: t()
def from_redix_error({:error, reason}) do
case reason do
%Redix.ConnectionError{reason: conn_reason} ->
connection_error("Redis连接错误", conn_reason)
%Redix.Error{message: message} ->
command_error("Redis命令错误", message)
:timeout ->
timeout_error("Redis操作超时", :timeout)
{:timeout, _} ->
timeout_error("Redis操作超时", reason)
:closed ->
network_error("Redis连接已关闭", :closed)
:noproc ->
connection_error("Redis连接进程不存在", :noproc)
%_{} = struct_error ->
unknown_error("Redis未知结构化错误", struct_error)
other ->
unknown_error("Redis未知错误", other)
end
end
def from_redix_error(error) do
unknown_error("非预期的Redis错误格式", error)
end
@doc """
分类并转换通用错误为 RedisPool 错误。
此函数是一个通用的错误转换工具,可以将各种格式的错误转换为标准的 RedisPool.Error 结构。
它首先检查错误是否已经是 RedisPool.Error 结构,如果不是,则根据错误类型进行分类和转换。
## 参数
- `error`: 一般错误,通常是 `{:error, reason}` 格式
## 返回值
返回标准化的 RedisPool.Error 结构体
## 示例
iex> RedisPool.Error.normalize_error({:error, :timeout})
%RedisPool.Error{code: :timeout_error, message: "操作超时", reason: :timeout}
iex> RedisPool.Error.normalize_error({:error, :econnrefused})
%RedisPool.Error{code: :network_error, message: "连接被拒绝", reason: :econnrefused}
iex> error = RedisPool.Error.connection_error("测试错误")
iex> RedisPool.Error.normalize_error({:error, error}) == error
true
"""
@spec normalize_error(term()) :: t()
def normalize_error({:error, reason}) do
case reason do
%__MODULE__{} = error ->
error
%Redix.ConnectionError{} ->
from_redix_error({:error, reason})
%Redix.Error{} ->
from_redix_error({:error, reason})
:timeout ->
timeout_error("操作超时", :timeout)
{:timeout, details} ->
timeout_error("操作超时", details)
:closed ->
network_error("连接已关闭", :closed)
:noproc ->
connection_error("进程不存在", :noproc)
:econnrefused ->
network_error("连接被拒绝", :econnrefused)
:ehostunreach ->
network_error("无法访问主机", :ehostunreach)
:enetunreach ->
network_error("网络不可达", :enetunreach)
:unauthorized ->
authentication_error("认证失败", :unauthorized)
{:EXIT, pid, reason} when is_pid(pid) ->
connection_error("连接进程异常退出", {:EXIT, pid, reason})
other ->
unknown_error("未知错误", other)
end
end
def normalize_error(error) do
unknown_error("非预期的错误格式", error)
end
@doc """
包装函数调用,捕获并转换错误。
此函数是一个高阶函数,用于包装其他函数调用,自动处理可能发生的各种错误。
它捕获异常和退出信号,将它们转换为标准的 RedisPool.Error 结构,
确保函数调用总是返回一致的成功或错误格式。
## 参数
- `fun`: 要执行的函数,一个无参数的函数
## 返回值
- `{:ok, result}` - 函数执行成功,返回函数的结果
- `{:error, %RedisPool.Error{}}` - 函数执行失败,返回标准化的错误结构
## 示例
iex> RedisPool.Error.wrap(fn -> {:ok, "success"} end)
{:ok, "success"}
iex> RedisPool.Error.wrap(fn -> {:error, :timeout} end)
{:error, %RedisPool.Error{code: :timeout_error, message: "操作超时", reason: :timeout}}
iex> RedisPool.Error.wrap(fn -> raise "测试异常" end)
{:error, %RedisPool.Error{code: :unknown_error, message: "执行过程中发生异常", reason: %RuntimeError{message: "测试异常"}}}
"""
@spec wrap((-> any())) :: {:ok, any()} | {:error, t()}
def wrap(fun) do
case fun.() do
{:ok, result} -> {:ok, result}
{:error, _} = error -> {:error, normalize_error(error)}
other -> {:ok, other}
end
rescue
e in [RedisPool.Error] -> {:error, e}
e -> {:error, unknown_error("执行过程中发生异常", e)}
catch
:exit, reason -> {:error, connection_error("连接进程异常退出", reason)}
error_type, error_value -> {:error, unknown_error("捕获到未处理的错误", {error_type, error_value})}
end
end