Packages
ash_graphql
0.8.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
require Ash.Query
def resolve(
%{arguments: arguments, context: context} = resolution,
{api, resource, %{type: :get, action: action, identity: identity}}
) do
opts = [
actor: Map.get(context, :actor),
authorize?: AshGraphql.Api.authorize?(api),
action: action
]
filter =
if identity do
resource
|> Ash.Resource.identities()
|> Enum.find(&(&1.name == identity))
|> Map.get(:keys)
|> Enum.map(fn key ->
{key, Map.get(arguments, key)}
end)
else
[id: Map.get(arguments, :id)]
end
result =
resource
|> Ash.Query.new()
|> Ash.Query.set_tenant(Map.get(context, :tenant))
|> Ash.Query.filter(^filter)
|> api.read_one(opts)
Absinthe.Resolution.put_result(resolution, to_resolution(result))
end
def resolve(
%{arguments: args, context: context, definition: %{selections: selections}} = resolution,
{api, resource, %{type: :list, action: action}}
) do
opts = [
actor: Map.get(context, :actor),
authorize?: AshGraphql.Api.authorize?(api),
action: action
]
page_opts =
args
|> Map.take([:limit, :offset, :after, :before])
|> Enum.reject(fn {_, val} -> is_nil(val) end)
opts =
case page_opts do
[] ->
opts
page_opts ->
if Enum.any?(selections, &(&1.name == :count)) do
page_opts = Keyword.put(page_opts, :count, true)
Keyword.put(opts, :page, page_opts)
else
Keyword.put(opts, :page, page_opts)
end
end
query =
case Map.fetch(args, :filter) do
{:ok, filter} ->
case Jason.decode(filter) do
{:ok, decoded} ->
Ash.Query.filter(resource, ^to_snake_case(decoded))
{:error, error} ->
raise "Error parsing filter: #{inspect(error)}"
end
_ ->
Ash.Query.new(resource)
end
query =
case Map.fetch(args, :sort) do
{:ok, sort} ->
keyword_sort =
Enum.map(sort, fn %{order: order, field: field} ->
{field, order}
end)
Ash.Query.sort(query, keyword_sort)
_ ->
query
end
result =
query
|> Ash.Query.set_tenant(Map.get(context, :tenant))
|> api.read(opts)
|> case do
{:ok, %{results: results, count: count}} ->
{:ok, %{results: results, count: count}}
{:ok, results} ->
{:ok, results}
error ->
error
end
Absinthe.Resolution.put_result(resolution, to_resolution(result))
end
def mutate(
%{arguments: %{input: input}, context: context} = resolution,
{api, resource, %{type: :create, action: action}}
) do
{attributes, relationships, arguments} = split_attrs_rels_and_args(input, resource)
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 = [
actor: Map.get(context, :actor),
authorize?: AshGraphql.Api.authorize?(api),
action: action
]
result =
changeset_with_relationships
|> Ash.Changeset.set_tenant(Map.get(context, :tenant))
|> Ash.Changeset.set_arguments(arguments)
|> api.create(opts)
|> case do
{:ok, value} ->
{:ok, %{result: value, errors: []}}
{:error, error} ->
{:ok, %{result: nil, errors: to_errors(error)}}
end
Absinthe.Resolution.put_result(resolution, to_resolution(result))
end
def mutate(
%{arguments: %{input: input} = arguments, context: context} = resolution,
{api, resource, %{type: :update, action: action, identity: identity}}
) do
filter =
if identity do
resource
|> Ash.Resource.identities()
|> Enum.find(&(&1.name == identity))
|> Map.get(:keys)
|> Enum.map(fn key ->
{key, Map.get(arguments, key)}
end)
else
[id: Map.get(arguments, :id)]
end
resource
|> Ash.Query.filter(^filter)
|> Ash.Query.set_tenant(Map.get(context, :tenant))
|> api.read_one!()
|> case do
nil ->
{:ok, %{result: nil, errors: [to_errors("not found")]}}
initial ->
{attributes, relationships, arguments} = split_attrs_rels_and_args(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 = [
actor: Map.get(context, :actor),
authorize?: AshGraphql.Api.authorize?(api),
action: action
]
result =
changeset_with_relationships
|> Ash.Changeset.set_tenant(Map.get(context, :tenant))
|> Ash.Changeset.set_arguments(arguments)
|> api.update(opts)
|> case do
{:ok, value} ->
{:ok, %{result: value, errors: []}}
{:error, error} ->
{:ok, %{result: nil, errors: List.wrap(error)}}
end
Absinthe.Resolution.put_result(resolution, to_resolution(result))
end
end
def mutate(
%{arguments: arguments, context: context} = resolution,
{api, resource, %{type: :destroy, action: action, identity: identity}}
) do
filter =
if identity do
resource
|> Ash.Resource.identities()
|> Enum.find(&(&1.name == identity))
|> Map.get(:keys)
|> Enum.map(fn key ->
{key, Map.get(arguments, key)}
end)
else
[id: Map.get(arguments, :id)]
end
resource
|> Ash.Query.filter(^filter)
|> Ash.Query.set_tenant(Map.get(context, :tenant))
|> api.read_one!()
|> case 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 =
initial
|> Ash.Changeset.new()
|> Ash.Changeset.set_tenant(Map.get(context, :tenant))
|> api.destroy(opts)
|> case 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_rels_and_args(input, resource) do
Enum.reduce(input, {%{}, %{}, %{}}, fn {key, value}, {attrs, rels, args} ->
cond do
Ash.Resource.public_attribute(resource, key) ->
{Map.put(attrs, key, value), rels, args}
Ash.Resource.public_relationship(resource, key) ->
{attrs, Map.put(rels, key, value), args}
true ->
{attrs, rels, Map.put(args, 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, arguments: args, context: %{loader: loader} = context} = resolution,
{api, relationship}
) do
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,
args: args,
tenant: Map.get(context, :tenant)
]
{batch_key, parent} = {{relationship.name, opts}, parent}
do_dataloader(resolution, loader, api, batch_key, args, parent)
end
defp do_dataloader(
resolution,
loader,
api,
batch_key,
_args,
parent
) do
loader = Dataloader.load(loader, api, batch_key, parent)
fun = fn loader ->
{:ok, Dataloader.get(loader, api, batch_key, parent)}
end
Absinthe.Resolution.put_result(
resolution,
{:middleware, Absinthe.Middleware.Dataloader, {loader, fun}}
)
end
defp apply_load_arguments(arguments, query) do
Enum.reduce(arguments, query, fn
{:limit, limit}, query ->
Ash.Query.limit(query, limit)
{:offset, offset}, query ->
Ash.Query.offset(query, offset)
{:filter, value}, query ->
decode_and_filter(query, value)
{:sort, value}, query ->
keyword_sort =
Enum.map(value, fn %{order: order, field: field} ->
{field, order}
end)
Ash.Query.sort(query, keyword_sort)
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
def to_snake_case(map) when is_map(map) do
Enum.into(map, %{}, fn {key, value} ->
{Macro.underscore(key), to_snake_case(value)}
end)
end
def to_snake_case(list) when is_list(list) do
Enum.map(list, &to_snake_case/1)
end
def 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