Packages
ash_graphql
0.2.1
1.10.0
1.9.4
1.9.3
1.9.2
1.9.1
1.9.0
1.8.5
1.8.4
1.8.3
1.8.2
1.8.1
1.8.0
1.7.17
1.7.16
1.7.15
1.7.14
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.0
1.5.1
1.5.0
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.4
1.3.3
1.3.2
1.3.1
1.3.0
1.2.1
1.2.0
1.1.1
1.1.0
1.0.1
1.0.0
1.0.0-rc.5
1.0.0-rc.4
retired
1.0.0-rc.3
1.0.0-rc.2
1.0.0-rc.1
1.0.0-rc.0
0.28.1
0.28.0
0.27.1
0.27.0
0.26.9
0.26.8
0.26.7
0.26.6
0.26.5
0.26.4
0.26.3
0.26.2
0.26.0
0.25.13
0.25.12
0.25.10
0.25.9
0.25.8
0.25.7
0.25.6
0.25.5
0.25.4
0.25.3
0.25.2
0.25.1
0.25.0
0.24.1
0.24.0
0.23.3
0.23.2
0.23.1
0.23.0
0.22.13
0.22.12
0.22.11
0.22.10
0.22.9
0.22.8
0.22.7
0.22.6
0.22.4
0.22.3
0.22.2
0.22.1
0.22.0
0.21.0
retired
0.20.5
0.20.4
0.20.3
0.20.2
0.20.1
0.20.0-rc.3
0.20.0-rc.2
0.20.0-rc.1
0.20.0-rc.0
0.19.0
0.18.0-rc0
0.17.5
0.17.5-rc0
0.17.4
0.17.2
0.17.1
0.17.0
0.16.28
0.16.27
0.16.26
0.16.25
0.16.24
0.16.23
0.16.22
0.16.21
0.16.20
0.16.18-rc5
0.16.18-rc4
0.16.18-rc3
0.16.18-rc2
0.16.18-rc1
0.16.18-rc0
0.16.17
0.16.16
0.16.15
0.16.14
0.16.13
0.16.12
0.16.11
0.16.10
0.16.9
0.16.8
0.16.7
0.16.6
0.16.5
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.10
0.15.9
0.15.8
0.15.7
0.15.6
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.1
0.14.0
0.13.1
0.13.0
0.12.5
0.12.4
0.12.3
0.12.1
0.12.0
0.10.0
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.0
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.3
0.6.2
0.6.1
0.6.0
0.5.0
0.4.0
0.3.2
0.3.1
0.3.0
0.2.1
0.2.0
0.1.3
0.1.2
The extension for building GraphQL APIs with Ash
Current section
Files
Jump to
Current section
Files
lib/graphql/resolver.ex
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} = split_attrs_and_rels(input, resource)
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} = split_attrs_and_rels(input, resource)
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 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
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 = nested_selections_with_pagination(field)
nested_loads = load_nested(relationship.destination, trimmed_nested)
query = Ash.Query.load(relationship.destination, nested_loads)
query = apply_load_arguments(field, query)
{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 apply_load_arguments(field, query) do
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 ->
decode_and_filter(query, value)
end)
end
defp nested_selections_with_pagination(field) do
Enum.flat_map(field.selections, fn nested ->
if nested.schema_node.identifier == :results do
nested.selections
else
[]
end
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
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