药剂UUID。 UUID不匹配时如何处理500错误

时间:2018-12-16 12:25:24

标签: postgresql elixir phoenix-framework uuid

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

4 个答案:

答案 0 :(得分:1)

这是UUIDBinary和其他类型的需要符合特定标准的已知问题(这是一个功能,不是bug™️)。就像提到的 @TheAnh 一样,您可以使用Ecto.UUID.dump/1来检查id是否有效,但我更希望直接对其进行挽救:

def get_user(id) do
  Repo.get(User, id)
rescue
  Ecto.Query.CastError -> nil
end

覆盖Repo

上面的示例可能很乏味,因为在调用rescue的任何地方都必须get。因此,我在get/3中覆盖了MyApp.Repo函数:

# 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_*来返回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

答案 1 :(得分: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

答案 2 :(得分:0)

我已经有了保护宏

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}

答案 3 :(得分: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|
    ```