ExEssentials.Web.Plugs.RequestValidator (ExEssentials v0.1.0)
View SourceA Plug
for validating request parameters using Ecto.Changeset
.
This plug enables modular and scalable validation of incoming request parameters by delegating the validation logic to a specific module (the :validator
). It dynamically calls a function named after the current :phoenix_action
, passing the request params
as an argument. The function must return an %Ecto.Changeset{}
.
If the changeset is valid, the request proceeds as usual. If invalid, a 400 Bad Request
response is sent in the application/problem+json
format.
Options
:validator
– (required) The module responsible for validation. It must implement a function for each action, with the same name as the:phoenix_action
and an arity of 1 (accepting theparams
).
Example Usage
Given a controller with the :create
action and a validator module:
defmodule MyAppWeb.Validators.Types.User.Preferences do
use Ecto.Schema
import Ecto.Changeset
@themes ~w(light dark)
@languages ~w(en pt-br)
@fields ~w(theme language)a
@primary_key false
embedded_schema do
field(:theme, :string)
field(:language, :string)
end
def changeset(schema, params) do
schema
|> cast(params, @fields)
|> validate_required(@fields)
|> validate_inclusion(:theme, @themes)
|> validate_inclusion(:language, @languages)
end
end
defmodule MyAppWeb.Validators.Types.User.Course do
use Ecto.Schema
import Ecto.Changeset
@fields ~w(name duration level)a
@levels ~w(beginner intermediary advanced)
@primary_key false
embedded_schema do
field(:name, :string)
field(:duration, :integer)
field(:level, :string)
end
def changeset(schema, params) do
schema
|> cast(params, @fields)
|> validate_required(@fields)
|> validate_number(:duration, greater_than: 0)
|> validate_inclusion(:level, @levels)
end
end
defmodule MyAppWeb.Validators.User do
use Ecto.Schema
import Ecto.Changeset
alias MyAppWeb.Validators.Types.User.Course
alias MyAppWeb.Validators.Types.User.Preferences
alias MyAppWeb.Validators.User
@fields ~w(name email)a
schema "users" do
field(:name, :string)
field(:email, :string)
embeds_one(:preferences, Preferences)
embeds_many(:courses, Course)
end
def create(params) do
%User{}
|> cast(params, @fields)
|> validate_required(@fields)
|> cast_embed(:preferences, required: true)
|> cast_embed(:courses, required: true)
end
end
You can plug the validator into your controller or router pipeline:
plug ExEssentials.Web.Plugs.RequestValidator, validator: MyAppWeb.Validators.User
Error Response Format
When validation fails, the response follows the structure inspired by RFC 7807 - Problem Details for HTTP APIs:
{
"errors": [
{
"code": "invalid_parameter",
"detail": "The 'name' field can't be blank",
"title": "Invalid request parameters"
},
{
"code": "invalid_parameter",
"detail": "The 'courses.[1].level' field is invalid",
"title": "Invalid request parameters"
},
{
"code": "invalid_parameter",
"detail": "The 'preferences.language' field can't be blank",
"title": "Invalid request parameters"
},
{
"code": "invalid_parameter",
"detail": "The 'preferences.theme' field is invalid",
"title": "Invalid request parameters"
}
],
"status_code": 400
}
Each validation error includes:
code
– configurable error code.detail
– human-readable description of the error.title
– general error title.
Configuration
You can customize certain aspects via your config.exs
:
config :ex_essentials, :web_request_validator,
json_library: Jason,
error_code: :invalid_parameter,
error_title: "Invalid request parameters"
By default, the module uses Jason to encode JSON responses, assigning the error code :invalid_parameter and the error title "Invalid request parameters".
Notes
- This plug assumes Phoenix is being used and requires
:phoenix_action
to be set inconn.private
. Ecto
is required for working with changesets.