Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

When id is invalid, Ecto raises:

Ecto.Query.CastError - cannot be dumped to type :binary_id in query. 

My get_user function:

query = from(u in User, where u.id == ^id)

case Repo.all(query) do
  [%User{} = user] -> {:ok, user}
  _ -> {:error, :not_found}
end

Is there any convenient way for handling this error to prevent 500 responses?

like image 462
Vlad Horbachevsky Avatar asked Dec 16 '18 12:12

Vlad Horbachevsky


2 Answers

That's a known issue with UUID, Binary and other ecto types that need to conform to a specific standard (It's a feature, not a bug™️). Like @TheAnh mentioned, you can use Ecto.UUID.dump/1 to check if the id is valid, but I prefer straight-up rescuing it:

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

Override Repo

The above example can get tedious because you'll have to rescue everywhere you call get. So I override the get/3 function in 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

Use fetch for Tuple format

You should use fetch_* method names instead of get_* to return values in tuple format (to avoid confusion with default Repo methods):

# 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

And call it like this in your main function:

def fetch_user(id) do
  Repo.fetch(User, id)
end
like image 80
Sheharyar Avatar answered Sep 18 '22 21:09

Sheharyar


I've ended up with 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

Usage:

def get_user(id) when is_uuid(id) do
  Repo.get(User, id)
end

def get_user(_id), do: {:error, :not_found}
like image 28
Vlad Horbachevsky Avatar answered Sep 22 '22 21:09

Vlad Horbachevsky