Elixir UUID. 当UUID不匹配时如何处理500错误

6
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响应?

1
你可以使用 Ecto.UUID.dump/1 来检查 ID。https://hexdocs.pm/ecto/Ecto.UUID.html#dump/1 - TheAnh
您能分享一下 get_user 函数的代码吗? - Sheharyar
@Sheharyar,请查看问题,已添加。 - Vlad Horbachevsky
4个回答

6

这是UUIDBinary和其他需要符合特定标准(这是一种特性,而不是错误™️)的类型的已知问题。正如@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获取元组格式数据

为了避免与默认的Repo方法混淆,应该使用fetch_*方法名来返回tuple格式的值:

# 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

3

我最终使用了守卫宏(guard macros)

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}

0
感谢@vlad-horbachevsky的回答,我将其扩展为我的守卫函数版本:

  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|
    ```

0

要返回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

问题稍作修改,我需要像下面这样的代码:defimpl Plug.Exception, for: Ecto.Query.CastError do def status(_exception), do: 404 end但这并没有什么帮助。 - Vlad Horbachevsky

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接