Hi, I’m using Ecto embedded schemas for input validation of a JSON API. Basically, I have a Plug.Router
implementation that accepts JSON and casts the input data into an Elixir struct with Ecto:
...
post "/validate" do
payload = conn.body_params
changeset = MyApp.Document.changeset(%MyApp.Document{}, payload)
if changeset.valid? do
document = Ecto.Changeset.apply_changes(changeset)
IO.inspect(document)
send_resp(conn, 200, "Payload: #{inspect(document)}\n")
else
IO.inspect(changeset.errors)
send_resp(conn, 400, "Error 400: Bad Request: #{inspect(changeset.errors)}\n")
end
end
...
My current implementation of the Ecto schema for MyApp.Document
is shown below.
As you can see, a Document
embeds a bunch of children, which for simplicity’s sake is now just Text
elements.
defmodule MyApp.Document do
use Ecto.Schema
import Ecto.Changeset
@primary_key false
embedded_schema do
field :id, Ecto.UUID
field :type, :string
embeds_many :children, MyApp.Text
end
def changeset(document, attrs) do
document
|> cast(attrs, [:id, :type])
|> cast_embed(:children)
|> validate_required([:id, :type])
|> validate_inclusion(:type, ["https://spec.nldoc.nl/Resource/Document"])
end
end
defmodule MyApp.Text do
use Ecto.Schema
import Ecto.Changeset
@primary_key false
embedded_schema do
field :id, Ecto.UUID
field :type, :string
field :text, :string
end
def changeset(text, attrs) do
text
|> cast(attrs, [:id, :type, :text])
|> validate_required([:id, :type, :text])
|> validate_inclusion(:type, ["https://spec.nldoc.nl/Resource/Text"])
end
end
The casting in my router works in the sense that if I supply a document with an invalid id or where the type is incorrect, then changeset.errors
in my router contains an error message and validation fails as expected (so 400 is returned).
However, if I have a similar problem in the any of the embedded objects, then the validation does indeed fail as expected, but changeset.errors
in my router is empty.
How do I ensure that any errors originating from casting / validating embedded schemas (MyApp.Text
), end up on the changeset returned by the parent (MyApp.Document
)? Or how else do I ensure that changeset.errors
in my router contains all validation errors, even from nested embeds?