I was trying to validate a :binary_id taken from a url string prior to passing on to queries and ran into what seems like a bug where Ecto.Type :binary_id accepts :string with no issue (as opposed to the a string formatted UUID).
Code:
defmodule TestBinaryId do
use Ecto.Schema
embedded_schema do
field(:test_uuid, :binary_id)
end
def changeset(attrs) do
Ecto.Changeset.cast(%TestBinaryId{}, attrs, [:test_uuid])
end
end
Did some more digging, looks like it’s expected behavior.
From Ecto.Type defp cast_fun(:binary_id), do: &cast_binary/1
Seems like @josevalim mentioned that the decision was to allow the repository to add errors (if I’m interpreting things correctly).
We have decided to treat team as binaries in the changeset and do the dump check when writing to the repository. We are going to introduce Repo.insert! and allow some errors to be added by the repository.
Where “treat team” I believe was meant to be “treat them (binary_id)”.
I’m using the params library for param sanitization at the controller level, and the fact that :binary_id can accept :binary values presents an issue at the repo level (as mentioned).
I suppose the answer here is to define my own custom Ecto.Type that will check for a UUID like pattern with binary matching?
I guess I just don’t understand the decision to not have more stringent type checks at the Ecto.Type.cast/2 level to allow for better sanitization prior to hitting the repo.
Might do some more digging in the Ecto.Query side of things to understand the logic for how the error is caught there and then try a custom Ecto.Type. Although I’ve already spent too much time down this rabbit hole on something that was intended to be a 10 min fix.
What are you using for the underlying type? PostgreSQL has an UUID type so it will do the validation for you, though you might need to translate the error into a changeset error.
I’m using :uuid for the database. I’m actually getting the error on a query when trying to fetch the record.
I was trying to validate the type in the controller in a defparams validation (params library). The example above is just demonstrating the underlying issue that a :binary_id type is cast as a :binary. This you have to hit the db to get an error (as you pointed out). I was trying to avoid hitting the db by finding the mismatched type at the web interface layer.
Crap… nevermind. Replacing :binary_id with Ecto.UUID does work. I swear it didn’t yesterday… I guess I got bit by the Friday afternoon “my brain shut down but I thought it was still working” situation.
defmodule TestBinaryId do
use Ecto.Schema
embedded_schema do
field(:test_uuid, Ecto.UUID)
end
def changeset(attrs) do
Ecto.Changeset.cast(%TestBinaryId{}, attrs, [:test_uuid])
end
end