Packages
ash_json_api
0.1.1
1.7.1
1.7.0
1.6.6
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0-rc.2
1.6.0-rc.1
1.6.0-rc.0
1.5.1
1.5.0
1.4.45
1.4.44
1.4.43
1.4.42
1.4.41
1.4.40
1.4.39
1.4.38
1.4.37
1.4.36
1.4.35
1.4.34
1.4.33
1.4.32
1.4.31
1.4.30
1.4.29
1.4.28
1.4.27
1.4.26
1.4.25
1.4.24
1.4.23
1.4.22
1.4.21
1.4.20
1.4.19
1.4.18
1.4.17
1.4.16
1.4.15
1.4.13
1.4.12
1.4.11
1.4.10
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.8
1.3.7
1.3.6
1.3.5
1.3.4
retired
1.3.3
1.3.2
1.3.1
1.3.0
1.2.2
1.2.1
1.2.0
1.1.2
1.1.1
1.1.0
1.0.0
1.0.0-rc.6
1.0.0-rc.5
1.0.0-rc.4
1.0.0-rc.3
1.0.0-rc.2
1.0.0-rc.1
1.0.0-rc.0
0.34.2
0.34.1
0.34.0
0.33.1
0.33.0
0.32.1
0.32.0
0.31.3
0.31.2
retired
0.31.1
0.31.0
0.30.1
0.30.0-rc.4
0.30.0-rc.3
0.30.0-rc.2
0.30.0-rc.1
0.30.0-rc.0
0.29.1
0.29.0
0.28.6
0.28.5
0.28.4
0.28.3
0.28.2
0.28.1
0.28.0
0.27.6
0.27.5
0.27.1
0.27.0
0.25.0
0.24.4
0.24.2
0.24.1
0.24.0
0.23.0
0.22.0
0.21.0
0.20.0
0.19.0
0.18.0
0.17.0
0.16.0
0.15.0
0.14.0
0.13.0
0.12.0
0.11.1
0.10.0
0.9.0
0.8.0
0.6.0
0.5.0
0.4.0
0.3.0
0.2.4
0.2.3
0.2.1
0.2.0
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
The JSON:API extension for the Ash Framework.
Current section
Files
Jump to
Current section
Files
lib/ash_json_api/controllers/helpers.ex
defmodule AshJsonApi.Controllers.Helpers do
@moduledoc """
Tools for control flow around a request, and common controller utilities.
`chain/2` lets us pipe cleanly, only doing stateful things if no errors
have been generated yet.
TODO: Ash will need to have its own concept of errors, and this
will need to convert those errors into API level errors.
"""
alias AshJsonApi.Controllers.Response
alias AshJsonApi.{Error, Request}
def render_or_render_errors(request, conn, function) do
chain(request, function,
fallback: fn request ->
Response.render_errors(conn, request)
end
)
end
def fetch_includes(request) do
chain(request, fn request ->
{new_result, includes} =
AshJsonApi.Includes.Includer.get_includes(request.assigns.result, request)
request
|> Request.assign(:result, new_result)
|> Request.assign(:includes, includes)
end)
end
def fetch_records(request) do
chain(request, fn request ->
params = %{
user: request.user,
side_load: request.includes_keyword,
action: request.action,
page: Map.get(request.assigns, :page, %{}),
authorize?: true,
filter: request.filter,
sort: request.sort
}
case request.api.read(request.resource, params) do
{:ok, paginator} ->
Request.assign(request, :result, paginator)
{:error, :unauthorized} ->
error = Error.Forbidden.new([])
Request.add_error(request, error)
{:error, db_error} ->
error =
Error.FrameworkError.new(
internal_description:
"Failed to read resource #{inspect(request.resource)} | #{inspect(db_error)}"
)
Request.add_error(request, error)
end
end)
end
def create_record(request) do
chain(request, fn %{api: api, resource: resource} ->
params = %{
user: request.user,
side_load: request.includes_keyword,
action: request.action,
authorize?: true,
attributes: request.attributes,
relationships: request.relationships
}
case api.create(resource, params) do
{:ok, record} ->
Request.assign(request, :result, record)
{:error, :unauthorized} ->
error = Error.Forbidden.new([])
Request.add_error(request, error)
{:error, _error} ->
error =
Error.FrameworkError.new(
internal_description:
"something went wrong while creating. Error messaging is incomplete so far."
)
Request.add_error(request, error)
end
end)
end
def fetch_record_from_path(request) do
request
|> fetch_id_path_param()
|> chain(fn %{api: api, resource: resource} = request ->
id = request.assigns.id
params = %{
user: request.user,
side_load: request.includes_keyword,
action: request.action,
authorize?: true
}
case api.get(resource, id, params) do
{:ok, nil} ->
error = Error.NotFound.new(id: id, resource: resource)
Request.add_error(request, error)
{:ok, record} ->
Request.assign(request, :result, record)
{:error, :unauthorized} ->
error = Error.Forbidden.new([])
Request.add_error(request, error)
{:error, db_error} ->
error =
Error.FrameworkError.new(
internal_description:
"failed to retrieve record by id for resource: #{inspect(resource)}, id: #{
inspect(id)
} | #{inspect(db_error)}"
)
Request.add_error(request, error)
end
end)
end
def fetch_id_path_param(request) do
chain(request, fn request ->
case request.path_params do
%{"id" => id} ->
Request.assign(request, :id, id)
_ ->
error =
Error.FrameworkError.new(
internal_description: "id path parameter not present in get route: #{request.url}"
)
Request.add_error(request, error)
end
end)
end
# This doesn't need to use chain, because its stateless and safe to
# do anytime. Returning multiple errors is a nice feature of JSON API
def fetch_pagination_parameters(request) do
request
|> add_limit()
|> add_offset()
end
defp add_limit(request) do
with %{"page" => page} <- request.query_params,
%{"limit" => limit} <- page,
{:integer, {integer, ""}} <- {:integer, Integer.parse(limit)} do
Request.update_assign(request, :page, %{limit: integer}, &Map.put(&1, :limit, integer))
else
{:integer, {_integer, _remaining}} ->
Request.add_error(request, Error.InvalidPagination.new(parameter: "page[limit]"))
_ ->
request
end
end
defp add_offset(request) do
with %{"page" => page} <- request.query_params,
%{"offset" => offset} <- page,
{:integer, {integer, ""}} <- {:integer, Integer.parse(offset)} do
Request.update_assign(request, :page, %{offset: integer}, &Map.put(&1, :offset, integer))
else
{:integer, {_integer, _remaining}} ->
Request.add_error(request, Error.InvalidPagination.new(parameter: "page[offset]"))
_ ->
request
end
end
def chain(request, func, opts \\ []) do
case request.errors do
[] ->
func.(request)
_ ->
case Keyword.fetch(opts, :fallback) do
{:ok, fallback} ->
fallback.(request)
_ ->
request
end
end
end
# @spec with_request(
# Plug.Conn.t(),
# Ash.resource(),
# Ash.action(),
# (AshJsonApi.Request.t() -> Plug.Conn.t())
# ) :: Plug.Conn.t()
# def with_request(conn, resource, action, function) do
# case AshJsonApi.Request.from(conn, resource, action) do
# %{errors: []} = request ->
# function.(request)
# %{errors: errors} = request ->
# Response.render_errors(conn, request, errors)
# end
# end
end