View Source PhoenixApiToolkit.GenericRequestValidator (Phoenix API Toolkit v3.1.2)
Request validator for generic (REST) requests. Meant to supplement database-level Ecto changesets. For example when creating a new entity, your database contexts / changesets will do their own validations and it would be useless to do so an extra time.
Suppose you have the following "users" resource:
- GET /api/users
- GET /api/users/{id}
You could have the following resource request validator and controller. The users context
is not present here, but if it uses PhoenixApiToolkit.Ecto.DynamicFilters
the processed
query parameters can be passed straight on and will result in a single filtered query.
defmodule MyUsersRequestValidator do
import Ecto.Changeset
import PhoenixApiToolkit.Ecto.Validators
import PhoenixApiToolkit.GenericRequestValidator
@schema resource_schema(%{username: :string}, %{date_of_birth: :date})
@entity_fields @schema |> get_entity_fields()
def index_query(attrs) do
@schema
|> query_order_by(attrs)
|> query_pagination(attrs)
|> cast(attrs, @entity_fields)
end
end
defmodule MyUsersController do
use MyAppWeb, :controller
import Plug.Conn
alias MyUsersRequestValidator, as: ReqVal
alias PhoenixApiToolkit.GenericRequestValidator, as: GenReqVal
def index(conn, _params) do
with %{valid?: true, changes: query_params} <- ReqVal.index_query(conn.query_params),
users <- MyUsersContext.list(query_params) do
conn |> send_resp(200, Jason.encode!(users))
else
_ -> conn |> send_resp(400, "idiot, your request is bad")
end
end
def show(conn, _params) do
with {valid?: true, changes: %{id: id}} <- GenReqVal.path_param(conn.path_params),
user when not is_nil(user) <- MyUsersContext.get(id) do
conn |> send_resp(200, Jason.encode!(user))
else
_ -> conn |> send_resp(400, "idiot, your request is bad")
end
end
end
Link to this section Summary
Functions
Get all the "non-meta" fields from a schema, that is, fields [:limit, :offset, :order_by, :lock_version]
are filtered out.
Validates the path parameter of a generic GET request of a RESTful resource.
Validates the order_by
query parameter of an index endpoint.
Validates the limit
and offset
query parameters of an index endpoint. If max_limit == nil
, no maximum limit is enforced.
Creates a generic schema for a REST resource.
Link to this section Types
A simple Ecto schema, embedded only, not coupled to a module or database entity
Link to this section Functions
Get all the "non-meta" fields from a schema, that is, fields [:limit, :offset, :order_by, :lock_version]
are filtered out.
examples
Examples
iex> resource_schema(%{first_name: :string}) |> get_entity_fields()
[:first_name, :id]
@spec path_param(map()) :: Ecto.Changeset.t()
Validates the path parameter of a generic GET request of a RESTful resource.
examples
Examples
# "id" is a required parameter
iex> path_param(%{}) |> Map.get(:valid?)
false
# "id" must be an integer
iex> path_param(%{"id" => "boom"}) |> Map.get(:valid?)
false
# "id" must be greater than 0
iex> path_param(%{"id" => 0}) |> Map.get(:valid?)
false
iex> path_param(%{"id" => 1}) |> Map.get(:valid?)
true
@spec query_order_by(map() | Ecto.Changeset.t() | schema(), map(), Enum.t()) :: Ecto.Changeset.t()
Validates the order_by
query parameter of an index endpoint.
examples
Examples
iex> resource_schema() |> query_order_by(%{"order_by" => "asc:last_name"}, ~w(last_name) |> MapSet.new())
#Ecto.Changeset<action: nil, changes: %{order_by: [asc: :last_name]}, errors: [], data: %{}, valid?: true>
See PhoenixApiToolkit.Ecto.Validators.validate_order_by/2
for more examples.
query_pagination(changeset, attrs, max_limit \\ 100, default_limit \\ 50)
View Source@spec query_pagination( map() | Ecto.Changeset.t() | schema(), map(), integer() | nil, integer() | nil ) :: Ecto.Changeset.t()
Validates the limit
and offset
query parameters of an index endpoint. If max_limit == nil
, no maximum limit is enforced.
examples
Examples
# the requested limit and offset should be in the range 0 - max_limit
iex> resource_schema() |> query_pagination(%{"limit" => 10}, 100)
#Ecto.Changeset<action: nil, changes: %{limit: 10}, errors: [], data: %{}, valid?: true>
iex> cs = resource_schema() |> query_pagination(%{"limit" => 150}, 100)
iex> cs.valid?
false
# a default limit can be set so that a default number of results is returned
iex> resource_schema() |> query_pagination(%{}, 100, 50)
#Ecto.Changeset<action: nil, changes: %{limit: 50}, errors: [], data: %{}, valid?: true>
# no max limit is enforced if `max_limit == nil`
iex> resource_schema() |> query_pagination(%{"limit" => 1_000_000}, nil)
#Ecto.Changeset<action: nil, changes: %{limit: 1000000}, errors: [], data: %{}, valid?: true>
# default limit can be disabled
iex> resource_schema() |> query_pagination(%{}, nil, nil)
#Ecto.Changeset<action: nil, changes: %{}, errors: [], data: %{}, valid?: true>
Creates a generic schema for a REST resource.
In general, REST resources will support an integer id
as a path parameter,
and the index endpoint will support order_by
, limit
and offset
.
Additionally, some endpoints will support a lock_version
for
optimistic locking using Ecto.Changeset.optimistic_lock/3
.
Additional fields can be passed along to the extra_fields
parameter. Fields that
can (usefully) be compared with smaller than / greater than comparisons can be passed
in comparables
. The value you pass AND a "_lt" (smaller than) and "_gte"
(greater than or equal to) variant will be added to the schema.
examples
Examples
# the result can be fed to cast/3
iex> resource_schema() |> Ecto.Changeset.cast(%{}, [])
#Ecto.Changeset<action: nil, changes: %{}, errors: [], data: %{}, valid?: true>
# an extended schema can be created by providing a map of fields
iex> resource_schema(%{first_name: :string})
{%{}, %{first_name: :string, id: :integer, limit: :integer, lock_version: :integer, offset: :integer, order_by: :string}}
# fields passed to the "comparables" parameter are added literally AND with _lt and _gte variants
iex> resource_schema(%{}, %{date_of_birth: :date}) |> elem(1) |> Map.has_key?(:date_of_birth_lt)
true