【问题标题】:Elixir UUID. How to handle 500 error when UUID doesn't match灵丹妙药 UUID。 UUID不匹配时如何处理500错误
【发布时间】:2021-06-03 23:02:39
【问题描述】:
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 响应?

【问题讨论】:

标签: postgresql elixir phoenix-framework uuid


【解决方案1】:

这是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

您应该使用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

【讨论】:

    【解决方案2】:

    我最终得到了保护宏

    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】:

      要返回 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 这样的东西,但它没有帮助
      【解决方案4】:

      感谢@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|
          ```
      

      【讨论】:

        猜你喜欢
        • 2018-06-12
        • 2019-02-15
        • 2018-10-11
        • 1970-01-01
        • 2019-11-30
        • 2017-10-28
        • 2010-10-17
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多