mirror of
https://github.com/ash-project/ash_graphql.git
synced 2024-09-20 13:24:14 +12:00
305 lines
8.5 KiB
Elixir
305 lines
8.5 KiB
Elixir
defmodule AshGraphql.Graphql.Resolver do
|
|
@moduledoc false
|
|
def resolve(
|
|
%{arguments: %{id: id}, context: context} = resolution,
|
|
{api, resource, :get, action}
|
|
) do
|
|
opts =
|
|
if AshGraphql.Api.authorize?(api) do
|
|
[actor: Map.get(context, :actor), action: action]
|
|
else
|
|
[action: action]
|
|
end
|
|
|
|
opts = Keyword.put(opts, :load, load_nested(resource, resolution.definition.selections))
|
|
|
|
result = api.get(resource, id, opts)
|
|
|
|
Absinthe.Resolution.put_result(resolution, to_resolution(result))
|
|
end
|
|
|
|
def resolve(
|
|
%{arguments: %{limit: limit, offset: offset} = args, context: context} = resolution,
|
|
{api, resource, :list, action}
|
|
) do
|
|
opts =
|
|
if AshGraphql.Api.authorize?(api) do
|
|
[actor: Map.get(context, :actor), action: action]
|
|
else
|
|
[action: action]
|
|
end
|
|
|
|
selections =
|
|
case Enum.find(resolution.definition.selections, &(&1.schema_node.identifier == :results)) do
|
|
nil ->
|
|
[]
|
|
|
|
field ->
|
|
field.selections
|
|
end
|
|
|
|
query =
|
|
resource
|
|
|> Ash.Query.limit(limit)
|
|
|> Ash.Query.offset(offset)
|
|
|> Ash.Query.load(load_nested(resource, selections))
|
|
|
|
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
|
|
|> api.read(opts)
|
|
|> case do
|
|
{:ok, results} ->
|
|
{:ok, %{results: results, count: Enum.count(results)}}
|
|
|
|
error ->
|
|
error
|
|
end
|
|
|
|
Absinthe.Resolution.put_result(resolution, to_resolution(result))
|
|
end
|
|
|
|
def mutate(
|
|
%{arguments: %{input: input}, context: context} = resolution,
|
|
{api, resource, :create, action}
|
|
) do
|
|
{attributes, relationships} =
|
|
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)
|
|
|
|
selections =
|
|
case Enum.find(resolution.definition.selections, &(&1.schema_node.identifier == :result)) do
|
|
nil ->
|
|
[]
|
|
|
|
field ->
|
|
field.selections
|
|
end
|
|
|
|
load = load_nested(resource, selections)
|
|
|
|
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)
|
|
|
|
opts =
|
|
if AshGraphql.Api.authorize?(api) do
|
|
[actor: Map.get(context, :actor), action: action]
|
|
else
|
|
[action: action]
|
|
end
|
|
|
|
result =
|
|
with {:ok, value} <- api.create(changeset_with_relationships, opts),
|
|
{:ok, value} <- api.load(value, load) do
|
|
{:ok, %{result: value, errors: []}}
|
|
else
|
|
{: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 ->
|
|
{attributes, relationships} =
|
|
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)
|
|
|
|
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)
|
|
|
|
opts =
|
|
if AshGraphql.Api.authorize?(api) do
|
|
[actor: Map.get(context, :actor), action: action]
|
|
else
|
|
[action: action]
|
|
end
|
|
|
|
selections =
|
|
case Enum.find(
|
|
resolution.definition.selections,
|
|
&(&1.schema_node.identifier == :result)
|
|
) do
|
|
nil ->
|
|
[]
|
|
|
|
field ->
|
|
field.selections
|
|
end
|
|
|
|
load = load_nested(resource, selections)
|
|
|
|
result =
|
|
with {:ok, value} <- api.update(changeset_with_relationships, opts),
|
|
{:ok, value} <- api.load(value, load) do
|
|
{:ok, %{result: value, errors: []}}
|
|
else
|
|
{: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
|
|
|
|
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
|
|
|
|
def resolve_assoc(%{source: parent} = resolution, {:one, name}) do
|
|
Absinthe.Resolution.put_result(resolution, {:ok, Map.get(parent, name)})
|
|
end
|
|
|
|
def resolve_assoc(%{source: parent} = resolution, {:many, name}) do
|
|
values = Map.get(parent, name)
|
|
paginator = %{results: values, count: Enum.count(values)}
|
|
|
|
Absinthe.Resolution.put_result(resolution, {:ok, paginator})
|
|
end
|
|
|
|
defp load_nested(resource, fields) do
|
|
Enum.map(fields, fn field ->
|
|
relationship = Ash.Resource.relationship(resource, field.schema_node.identifier)
|
|
|
|
cond do
|
|
!relationship ->
|
|
field.schema_node.identifier
|
|
|
|
relationship.cardinality == :many ->
|
|
trimmed_nested =
|
|
Enum.flat_map(field.selections, fn nested ->
|
|
if nested.schema_node.identifier == :results do
|
|
nested.selections
|
|
else
|
|
[nested]
|
|
end
|
|
end)
|
|
|
|
nested_loads = load_nested(relationship.destination, trimmed_nested)
|
|
|
|
query = Ash.Query.load(relationship.destination, nested_loads)
|
|
|
|
query =
|
|
Enum.reduce(field.arguments, query, fn
|
|
%{name: "limit", value: value}, query ->
|
|
Ash.Query.limit(query, value)
|
|
|
|
%{name: "offset", value: value}, query ->
|
|
Ash.Query.offset(query, value)
|
|
|
|
%{name: "filter", value: value}, query ->
|
|
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)
|
|
|
|
{field.schema_node.identifier, query}
|
|
|
|
true ->
|
|
nested_loads = load_nested(relationship.destination, field.selections)
|
|
|
|
query = Ash.Query.load(relationship.destination, nested_loads)
|
|
{field.schema_node.identifier, query}
|
|
end
|
|
end)
|
|
end
|
|
|
|
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
|
|
|
|
defp to_resolution({:ok, value}), do: {:ok, value}
|
|
|
|
defp to_resolution({:error, error}),
|
|
do: {:error, error |> List.wrap() |> Enum.map(&Exception.message(&1))}
|
|
end
|