长生不老药 UUID。 UUID不匹配时如何处理500错误
Elixir UUID. How to handle 500 error when UUID doesn't match
def show(conn, %{"id" => id}) do
with {:ok, user} <- UserAction.get_user(id)
|> put_status(200)
|> render("show.json", %{user: user})
else
{:error, :not_found} -> {:error, :not_found, %User{id: id}}
end
end
当id无效时,Ecto
引发:
Ecto.Query.CastError - cannot be dumped to type :binary_id in query.
我的 get_user
函数:
query = from(u in User, where u.id == ^id)
case Repo.all(query) do
[%User{} = user] -> {:ok, user}
_ -> {:error, :not_found}
end
是否有任何方便的方法来处理此错误以防止 500 响应?
到 return 400 你需要用适当的状态更新 conn
然后渲染。
conn
|> put_status(:not_found)
|> put_view(YourApp.ErrorView)
|> render("404.html")
这将在 with
表达式的 else 子句中执行。您可以通过自定义错误进一步了解这个想法:https://hexdocs.pm/phoenix/errors.html
如果可能的话,我建议在操作之前检查输入是否无效。
你有几种方法。可以在尝试执行数据库查询之前进行验证。一种可靠的方法是在尝试查询之前尝试并转换值。
iex(1)> Ecto.UUID.cast("no good")
:error
iex(2)> Ecto.UUID.cast("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")
:error
iex(3)> Ecto.UUID.cast("de851708-7f7a-40e1-b8ec-da2baec30839")
{:ok, "de851708-7f7a-40e1-b8ec-da2baec30839"}
鉴于上述行为,您可以将 with
表达式包装在 case 表达式中。虽然,我不确定我是否会在这里使用 with
,它对管道更好。
case Ecto.UUID.cast(id) do
:error ->
conn |> put_status(400) |> render("error.json", %{message: "Some Message"})
{:ok, uuid} ->
case UserAction.get_user(uuid) do
{:ok, user} ->
conn |> put_status(200) |> render("show.json", %{user: user})
_ ->
conn |> put_status(404) |> render("error.json", %{message: "User not found"})
end
end
这是 UUID
、Binary
和其他需要符合特定标准的 ecto 类型的已知问题(这是一个功能,而不是错误™️)。就像@TheAnh提到的,你可以使用Ecto.UUID.dump/1
来检查id
是否有效,但我更喜欢直接拯救它:
def get_user(id) do
Repo.get(User, id)
rescue
Ecto.Query.CastError -> nil
end
覆盖Repo
上面的示例可能会变得乏味,因为您必须在调用 get
的所有地方 rescue
。所以我覆盖 MyApp.Repo
中的 get/3
函数:
# lib/my_app/repo.ex
defoverridable [get: 2, get: 3]
def get(query, id, opts \ []) do
super(query, id, opts)
rescue
Ecto.Query.CastError -> nil
end
使用 fetch
作为元组格式
您应该使用 fetch_*
方法名称而不是 get_*
到 return 值 tuple
格式(以避免与默认 Repo
方法混淆):
# lib/my_app/repo.ex
def fetch(query, id, opts \ []) do
case get(query, id, opts) do
nil -> {:error, :not_found}
schema -> {:ok, schema}
end
end
然后在你的主函数中这样调用它:
def fetch_user(id) do
Repo.fetch(User, id)
end
我得到了保护宏
defmacro is_uuid(value) do
quote do
is_binary(unquote(value)) and byte_size(unquote(value)) == 36 and
binary_part(unquote(value), 8, 1) == "-" and binary_part(unquote(value), 13, 1) == "-" and
binary_part(unquote(value), 18, 1) == "-" and binary_part(unquote(value), 23, 1) == "-"
end
end
用法:
def get_user(id) when is_uuid(id) do
Repo.get(User, id)
end
def get_user(_id), do: {:error, :not_found}
感谢@vlad-horbachevsky 的回答,我将它扩展到我的 guard 函数版本:
defguard is_uuid(value)
when is_binary(value) and byte_size(value) == 36 and
binary_part(value, 1, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 2, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 3, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 4, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 5, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 6, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 7, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 8, 1) == "-" and
binary_part(value, 9, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 10, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 11, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 12, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 13, 1) == "-" and
binary_part(value, 14, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 15, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 16, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 17, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 18, 1) == "-" and
binary_part(value, 19, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 20, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 21, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 22, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 23, 1) == "-" and
binary_part(value, 24, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 25, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 26, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 27, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 28, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 29, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 30, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 31, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 32, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 33, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 34, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 35, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F|
```
def show(conn, %{"id" => id}) do
with {:ok, user} <- UserAction.get_user(id)
|> put_status(200)
|> render("show.json", %{user: user})
else
{:error, :not_found} -> {:error, :not_found, %User{id: id}}
end
end
当id无效时,Ecto
引发:
Ecto.Query.CastError - cannot be dumped to type :binary_id in query.
我的 get_user
函数:
query = from(u in User, where u.id == ^id)
case Repo.all(query) do
[%User{} = user] -> {:ok, user}
_ -> {:error, :not_found}
end
是否有任何方便的方法来处理此错误以防止 500 响应?
到 return 400 你需要用适当的状态更新 conn
然后渲染。
conn
|> put_status(:not_found)
|> put_view(YourApp.ErrorView)
|> render("404.html")
这将在 with
表达式的 else 子句中执行。您可以通过自定义错误进一步了解这个想法:https://hexdocs.pm/phoenix/errors.html
如果可能的话,我建议在操作之前检查输入是否无效。 你有几种方法。可以在尝试执行数据库查询之前进行验证。一种可靠的方法是在尝试查询之前尝试并转换值。
iex(1)> Ecto.UUID.cast("no good")
:error
iex(2)> Ecto.UUID.cast("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")
:error
iex(3)> Ecto.UUID.cast("de851708-7f7a-40e1-b8ec-da2baec30839")
{:ok, "de851708-7f7a-40e1-b8ec-da2baec30839"}
鉴于上述行为,您可以将 with
表达式包装在 case 表达式中。虽然,我不确定我是否会在这里使用 with
,它对管道更好。
case Ecto.UUID.cast(id) do
:error ->
conn |> put_status(400) |> render("error.json", %{message: "Some Message"})
{:ok, uuid} ->
case UserAction.get_user(uuid) do
{:ok, user} ->
conn |> put_status(200) |> render("show.json", %{user: user})
_ ->
conn |> put_status(404) |> render("error.json", %{message: "User not found"})
end
end
这是 UUID
、Binary
和其他需要符合特定标准的 ecto 类型的已知问题(这是一个功能,而不是错误™️)。就像@TheAnh提到的,你可以使用Ecto.UUID.dump/1
来检查id
是否有效,但我更喜欢直接拯救它:
def get_user(id) do
Repo.get(User, id)
rescue
Ecto.Query.CastError -> nil
end
覆盖Repo
上面的示例可能会变得乏味,因为您必须在调用 get
的所有地方 rescue
。所以我覆盖 MyApp.Repo
中的 get/3
函数:
# lib/my_app/repo.ex
defoverridable [get: 2, get: 3]
def get(query, id, opts \ []) do
super(query, id, opts)
rescue
Ecto.Query.CastError -> nil
end
使用 fetch
作为元组格式
您应该使用 fetch_*
方法名称而不是 get_*
到 return 值 tuple
格式(以避免与默认 Repo
方法混淆):
# lib/my_app/repo.ex
def fetch(query, id, opts \ []) do
case get(query, id, opts) do
nil -> {:error, :not_found}
schema -> {:ok, schema}
end
end
然后在你的主函数中这样调用它:
def fetch_user(id) do
Repo.fetch(User, id)
end
我得到了保护宏
defmacro is_uuid(value) do
quote do
is_binary(unquote(value)) and byte_size(unquote(value)) == 36 and
binary_part(unquote(value), 8, 1) == "-" and binary_part(unquote(value), 13, 1) == "-" and
binary_part(unquote(value), 18, 1) == "-" and binary_part(unquote(value), 23, 1) == "-"
end
end
用法:
def get_user(id) when is_uuid(id) do
Repo.get(User, id)
end
def get_user(_id), do: {:error, :not_found}
感谢@vlad-horbachevsky 的回答,我将它扩展到我的 guard 函数版本:
defguard is_uuid(value)
when is_binary(value) and byte_size(value) == 36 and
binary_part(value, 1, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 2, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 3, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 4, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 5, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 6, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 7, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 8, 1) == "-" and
binary_part(value, 9, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 10, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 11, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 12, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 13, 1) == "-" and
binary_part(value, 14, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 15, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 16, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 17, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 18, 1) == "-" and
binary_part(value, 19, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 20, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 21, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 22, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 23, 1) == "-" and
binary_part(value, 24, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 25, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 26, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 27, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 28, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 29, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 30, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 31, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 32, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 33, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 34, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F| and
binary_part(value, 35, 1) in ~w|0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F|
```