基于Plug的Elixir REST API自定义MatchError优雅处理咨询
好问题!在Elixir/Plug里处理这类业务级的匹配错误,确实不能直接依赖默认的MatchError——它属于系统级异常,会被当成500错误返回,完全没法传递自定义业务信息。下面给你几个简洁的实现思路,完全能满足你返回自定义错误的需求:
1. 先定义专属的业务异常模块
首先我们要把业务错误和系统错误区分开,创建一个自定义异常:
defmodule MyApp.BusinessError do defexception [:code, :reason, :status] # 定义异常的默认消息,方便调试 def message(%__MODULE__{code: code, reason: reason}) do "Business Error #{code}: #{reason}" end end
这里给异常加上了code(业务错误码)、reason(错误描述)和status(HTTP状态码,比如422表示请求语义错误)三个字段,完全贴合你的需求。
2. 替换直接模式匹配,改用显式判断+抛自定义异常
你原来的0 = DbAdapter.select(...)会直接抛出MatchError,我们改成显式判断结果,然后抛出自己定义的业务异常:
# 原来的写法(会触发MatchError) # 0 = DbAdapter.select("SELECT * FROM users WHERE email = 'email@example.com';") # 改进后的写法(建议用参数化查询避免SQL注入) case DbAdapter.select("SELECT * FROM users WHERE email = $1;", [email]) do [] -> :ok # 没找到用户,继续创建流程 _users -> raise MyApp.BusinessError, code: 1113, reason: "account already exists", status: 422 end
3. 在Plug的handle_errors中捕获自定义异常
现在修改全局的错误处理函数,匹配我们的业务异常,返回格式化后的响应:
def handle_errors(conn, %{reason: %MyApp.BusinessError{code: code, reason: reason, status: status}}) do # 用Jason把错误序列化成JSON返回(确保你已经添加了Jason依赖) error_body = Jason.encode!(%{code: code, message: reason}) send_resp(conn, status, error_body) end # 保留兜底的系统错误处理 def handle_errors(conn, _error_details) do default_error = Jason.encode!(%{code: 500, message: "Internal server error"}) send_resp(conn, 500, default_error) end
更优雅的进阶写法:用with串联业务逻辑
如果你的创建用户流程有多个步骤(比如参数验证、邮箱检查、插入数据库),用with可以让代码更扁平清晰,甚至不用走到全局的handle_errors:
def create_user(conn, params) do with {:ok, email} <- validate_email(params), :ok <- check_email_not_exists(email), {:ok, user} <- insert_user(params) do # 成功流程 send_resp(conn, 201, Jason.encode!(%{data: user})) else {:error, %MyApp.BusinessError{} = err} -> # 直接在业务函数内返回自定义错误响应 error_body = Jason.encode!(%{code: err.code, message: err.reason}) send_resp(conn, err.status, error_body) {:error, reason} -> # 处理其他非业务类错误 send_resp(conn, 400, Jason.encode!(%{code: 400, message: reason})) end end defp check_email_not_exists(email) do case DbAdapter.select("SELECT * FROM users WHERE email = $1;", [email]) do [] -> :ok _ -> {:error, %MyApp.BusinessError{code: 1113, reason: "account already exists", status: 422}} end end
小提示:别用模式匹配做业务判断
Elixir的模式匹配更适合结构匹配(比如匹配函数返回的{:ok, data}或{:error, reason}),而业务级的存在性判断,用case显式处理会更清晰,也更容易维护。
内容的提问来源于stack exchange,提问作者widavies
相关产品推荐
相关产品推荐

