ash_graphql/lib/graphql/resolver.ex

269 lines
7 KiB
Elixir
Raw Normal View History

2020-05-02 10:35:12 +12:00
defmodule AshGraphql.Graphql.Resolver do
2020-08-14 10:55:34 +12:00
@moduledoc false
2020-05-02 10:35:12 +12:00
def resolve(
%{arguments: %{id: id}, context: context} = resolution,
{api, resource, :get, action}
) do
2020-09-29 02:42:36 +13:00
opts = [
actor: Map.get(context, :actor),
authorize?: AshGraphql.Api.authorize?(api),
action: action
]
2020-06-06 06:41:00 +12:00
result = api.get(resource, id, opts)
2020-05-02 10:35:12 +12:00
Absinthe.Resolution.put_result(resolution, to_resolution(result))
end
def resolve(
2020-08-14 09:39:59 +12:00
%{arguments: %{limit: limit, offset: offset} = args, context: context} = resolution,
{api, resource, :list, action}
2020-05-02 10:35:12 +12:00
) do
2020-09-29 02:42:36 +13:00
opts = [
actor: Map.get(context, :actor),
authorize?: AshGraphql.Api.authorize?(api),
action: action
]
2020-06-06 06:41:00 +12:00
2020-08-14 09:39:59 +12:00
query =
resource
|> Ash.Query.limit(limit)
|> Ash.Query.offset(offset)
2020-08-14 09:39:59 +12:00
query =
case Map.fetch(args, :filter) do
{:ok, filter} ->
case Jason.decode(filter) do
{:ok, decoded} ->
Ash.Query.filter(query, to_snake_case(decoded))
{:error, error} ->
raise "Error parsing filter: #{inspect(error)}"
end
_ ->
query
end
result =
query
2020-06-06 06:41:00 +12:00
|> api.read(opts)
|> case do
{:ok, results} ->
2020-08-14 10:55:34 +12:00
{:ok, %{results: results, count: Enum.count(results)}}
error ->
error
2020-05-02 10:35:12 +12:00
end
Absinthe.Resolution.put_result(resolution, to_resolution(result))
end
2020-08-14 09:39:59 +12:00
def mutate(
%{arguments: %{input: input}, context: context} = resolution,
{api, resource, :create, action}
) do
2020-08-15 02:20:47 +12:00
{attributes, relationships} = split_attrs_and_rels(input, resource)
2020-08-14 09:39:59 +12:00
changeset = Ash.Changeset.new(resource, attributes)
changeset_with_relationships =
Enum.reduce(relationships, changeset, fn {relationship, replacement}, changeset ->
Ash.Changeset.replace_relationship(changeset, relationship, replacement)
end)
2020-09-29 02:42:36 +13:00
opts = [
actor: Map.get(context, :actor),
authorize?: AshGraphql.Api.authorize?(api),
action: action
]
2020-08-14 09:39:59 +12:00
result =
2020-09-24 12:54:57 +12:00
case api.create(changeset_with_relationships, opts) do
{:ok, value} ->
{:ok, %{result: value, errors: []}}
2020-08-14 09:39:59 +12:00
{:error, error} ->
{:ok, %{result: nil, errors: to_errors(error)}}
end
Absinthe.Resolution.put_result(resolution, to_resolution(result))
end
def mutate(
%{arguments: %{id: id, input: input}, context: context} = resolution,
{api, resource, :update, action}
) do
case api.get(resource, id) do
nil ->
{:ok, %{result: nil, errors: [to_errors("not found")]}}
initial ->
2020-08-15 02:20:47 +12:00
{attributes, relationships} = split_attrs_and_rels(input, resource)
2020-08-14 09:39:59 +12:00
changeset = Ash.Changeset.new(initial, attributes)
changeset_with_relationships =
Enum.reduce(relationships, changeset, fn {relationship, replacement}, changeset ->
Ash.Changeset.replace_relationship(changeset, relationship, replacement)
end)
2020-09-29 02:42:36 +13:00
opts = [
actor: Map.get(context, :actor),
authorize?: AshGraphql.Api.authorize?(api),
action: action
]
2020-08-14 09:39:59 +12:00
result =
2020-09-24 12:54:57 +12:00
case api.update(changeset_with_relationships, opts) do
{:ok, value} ->
{:ok, %{result: value, errors: []}}
2020-08-14 09:39:59 +12:00
{:error, error} ->
{:ok, %{result: nil, errors: List.wrap(error)}}
end
Absinthe.Resolution.put_result(resolution, to_resolution(result))
end
end
def mutate(%{arguments: %{id: id}, context: context} = resolution, {api, resource, action}) do
case api.get(resource, id) do
nil ->
{:ok, %{result: nil, errors: [to_errors("not found")]}}
initial ->
opts =
if AshGraphql.Api.authorize?(api) do
[actor: Map.get(context, :actor), action: action]
else
[action: action]
end
result =
case api.destroy(initial, opts) do
:ok -> {:ok, %{result: initial, errors: []}}
{:error, error} -> {:ok, %{result: nil, errors: to_errors(error)}}
end
Absinthe.Resolution.put_result(resolution, to_resolution(result))
end
end
2020-08-15 02:20:47 +12:00
defp split_attrs_and_rels(input, resource) do
Enum.reduce(input, {%{}, %{}}, fn {key, value}, {attrs, rels} ->
if Ash.Resource.attribute(resource, key) do
{Map.put(attrs, key, value), rels}
else
{attrs, Map.put(rels, key, value)}
end
end)
end
2020-08-14 09:39:59 +12:00
defp to_errors(errors) do
errors
|> List.wrap()
|> Enum.map(fn error ->
cond do
is_binary(error) ->
%{message: error}
Exception.exception?(error) ->
%{
message: Exception.message(error)
}
true ->
%{message: "something went wrong"}
end
end)
end
2020-09-24 12:54:57 +12:00
def resolve_assoc(
2020-09-29 02:42:36 +13:00
%{source: parent, arguments: args, context: %{ash_loader: loader} = context} = resolution,
2020-09-24 12:54:57 +12:00
{api, relationship}
) do
2020-09-29 02:42:36 +13:00
api_opts = [actor: Map.get(context, :actor), authorize?: AshGraphql.Api.authorize?(api)]
opts = [
query: apply_load_arguments(args, Ash.Query.new(relationship.destination)),
api_opts: api_opts
]
2020-09-24 12:54:57 +12:00
{batch_key, parent} = {{relationship.name, opts}, parent}
2020-08-14 09:39:59 +12:00
2020-09-29 02:42:36 +13:00
do_dataloader(resolution, loader, api, batch_key, args, parent)
2020-08-14 09:39:59 +12:00
end
2020-09-24 12:54:57 +12:00
defp do_dataloader(
resolution,
loader,
api,
batch_key,
args,
2020-09-29 02:42:36 +13:00
parent
2020-09-24 12:54:57 +12:00
) do
loader = Dataloader.load(loader, api, batch_key, parent)
fun = fn loader ->
2020-09-29 02:42:36 +13:00
callback = default_callback(loader)
2020-09-24 12:54:57 +12:00
loader
|> Dataloader.get(api, batch_key, parent)
|> callback.(parent, args)
end
2020-08-14 09:39:59 +12:00
2020-09-24 12:54:57 +12:00
Absinthe.Resolution.put_result(
resolution,
{:middleware, Absinthe.Middleware.Dataloader, {loader, fun}}
)
end
2020-08-14 09:39:59 +12:00
2020-09-24 12:54:57 +12:00
defp default_callback(%{options: loader_options}) do
if loader_options[:get_policy] == :tuples do
fn result, _parent, _args -> result end
else
fn result, _parent, _args -> {:ok, result} end
end
2020-08-14 09:39:59 +12:00
end
2020-09-24 12:54:57 +12:00
defp apply_load_arguments(arguments, query) do
Enum.reduce(arguments, query, fn
{:limit, limit}, query ->
Ash.Query.limit(query, limit)
2020-08-15 02:20:47 +12:00
2020-09-24 12:54:57 +12:00
{:offset, offset}, query ->
Ash.Query.offset(query, offset)
2020-08-15 02:20:47 +12:00
2020-09-24 12:54:57 +12:00
{:filter, value}, query ->
2020-08-15 02:20:47 +12:00
decode_and_filter(query, value)
end)
end
defp decode_and_filter(query, value) do
case Jason.decode(value) do
{:ok, decoded} ->
Ash.Query.filter(query, to_snake_case(decoded))
{:error, error} ->
raise "Error parsing filter: #{inspect(error)}"
end
end
2020-08-14 09:39:59 +12:00
defp to_snake_case(map) when is_map(map) do
Enum.into(map, %{}, fn {key, value} ->
{Macro.underscore(key), to_snake_case(value)}
end)
end
defp to_snake_case(list) when is_list(list) do
Enum.map(list, &to_snake_case/1)
end
defp to_snake_case(other), do: other
2020-05-02 10:35:12 +12:00
defp to_resolution({:ok, value}), do: {:ok, value}
2020-08-14 09:39:59 +12:00
defp to_resolution({:error, error}),
do: {:error, error |> List.wrap() |> Enum.map(&Exception.message(&1))}
2020-05-02 10:35:12 +12:00
end