2020-05-02 10:35:12 +12:00
|
|
|
defmodule AshGraphql.Graphql.Resolver do
|
2020-08-14 10:55:34 +12:00
|
|
|
@moduledoc false
|
2020-10-10 14:10:22 +13:00
|
|
|
|
|
|
|
require Ash.Query
|
2021-04-14 09:49:10 +12:00
|
|
|
require Logger
|
2022-11-23 07:27:53 +13:00
|
|
|
import AshGraphql.TraceHelpers
|
2020-10-10 14:17:34 +13:00
|
|
|
|
2023-02-21 06:48:12 +13:00
|
|
|
def resolve(%Absinthe.Resolution{state: :resolved} = resolution, _),
|
|
|
|
do: resolution
|
|
|
|
|
2020-05-02 10:35:12 +12:00
|
|
|
def resolve(
|
2020-11-18 20:14:33 +13:00
|
|
|
%{arguments: arguments, context: context} = resolution,
|
2021-06-30 04:17:33 +12:00
|
|
|
{api, resource,
|
2022-11-23 07:27:53 +13:00
|
|
|
%{
|
|
|
|
name: query_name,
|
|
|
|
type: :get,
|
|
|
|
action: action,
|
|
|
|
identity: identity,
|
|
|
|
modify_resolution: modify
|
|
|
|
} = gql_query}
|
2020-05-02 10:35:12 +12:00
|
|
|
) do
|
2023-02-16 02:20:35 +13:00
|
|
|
case handle_arguments(resource, action, arguments) do
|
|
|
|
{:ok, arguments} ->
|
|
|
|
metadata = %{
|
|
|
|
api: api,
|
|
|
|
resource: resource,
|
|
|
|
resource_short_name: Ash.Resource.Info.short_name(resource),
|
|
|
|
actor: Map.get(context, :actor),
|
|
|
|
tenant: Map.get(context, :tenant),
|
|
|
|
action: action,
|
|
|
|
source: :graphql,
|
|
|
|
query: query_name,
|
|
|
|
authorize?: AshGraphql.Api.Info.authorize?(api)
|
|
|
|
}
|
2021-06-30 04:17:33 +12:00
|
|
|
|
2023-02-16 02:20:35 +13:00
|
|
|
trace api,
|
|
|
|
resource,
|
|
|
|
:gql_query,
|
|
|
|
query_name,
|
|
|
|
metadata do
|
|
|
|
opts = [
|
|
|
|
actor: Map.get(context, :actor),
|
|
|
|
action: action,
|
|
|
|
verbose?: AshGraphql.Api.Info.debug?(api)
|
|
|
|
]
|
|
|
|
|
|
|
|
filter = identity_filter(identity, resource, arguments)
|
|
|
|
|
|
|
|
query =
|
|
|
|
resource
|
|
|
|
|> Ash.Query.new()
|
|
|
|
|> Ash.Query.set_tenant(Map.get(context, :tenant))
|
|
|
|
|> Ash.Query.set_context(Map.get(context, :ash_context) || %{})
|
|
|
|
|> set_query_arguments(action, arguments)
|
|
|
|
|> select_fields(resource, resolution)
|
|
|
|
|
|
|
|
{result, modify_args} =
|
|
|
|
case filter do
|
|
|
|
{:ok, filter} ->
|
|
|
|
query
|
|
|
|
|> Ash.Query.do_filter(filter)
|
|
|
|
|> load_fields(resource, api, resolution)
|
|
|
|
|> case do
|
|
|
|
{:ok, query} ->
|
|
|
|
result =
|
|
|
|
query
|
|
|
|
|> Ash.Query.for_read(action, %{},
|
|
|
|
actor: opts[:actor],
|
|
|
|
authorize?: AshGraphql.Api.Info.authorize?(api)
|
|
|
|
)
|
|
|
|
|> api.read_one(opts)
|
2022-11-23 07:27:53 +13:00
|
|
|
|
2023-02-16 02:20:35 +13:00
|
|
|
{result, [query, result]}
|
|
|
|
|
|
|
|
{:error, error} ->
|
|
|
|
{{:error, error}, [query, {:error, error}]}
|
|
|
|
end
|
2022-11-23 07:27:53 +13:00
|
|
|
|
|
|
|
{:error, error} ->
|
2023-02-16 02:20:35 +13:00
|
|
|
query =
|
|
|
|
resource
|
|
|
|
|> Ash.Query.new()
|
|
|
|
|> Ash.Query.set_tenant(Map.get(context, :tenant))
|
|
|
|
|> Ash.Query.set_context(Map.get(context, :ash_context) || %{})
|
|
|
|
|> set_query_arguments(action, arguments)
|
|
|
|
|> select_fields(resource, resolution)
|
|
|
|
|> load_fields(resource, api, resolution)
|
|
|
|
|
2022-11-23 07:27:53 +13:00
|
|
|
{{:error, error}, [query, {:error, error}]}
|
|
|
|
end
|
2021-06-30 04:17:33 +12:00
|
|
|
|
2023-02-16 02:20:35 +13:00
|
|
|
case {result, gql_query.allow_nil?} do
|
|
|
|
{{:ok, nil}, false} ->
|
|
|
|
{:ok, filter} = filter
|
|
|
|
result = not_found(filter, resource, context, api)
|
2020-05-02 10:35:12 +12:00
|
|
|
|
2023-02-16 02:20:35 +13:00
|
|
|
resolution
|
|
|
|
|> Absinthe.Resolution.put_result(result)
|
|
|
|
|> add_root_errors(api, result)
|
2021-09-13 08:39:48 +12:00
|
|
|
|
2023-02-16 02:20:35 +13:00
|
|
|
{result, _} ->
|
|
|
|
resolution
|
|
|
|
|> Absinthe.Resolution.put_result(
|
|
|
|
to_resolution(
|
|
|
|
result
|
|
|
|
|> add_read_metadata(
|
|
|
|
gql_query,
|
|
|
|
Ash.Resource.Info.action(query.resource, action)
|
|
|
|
),
|
|
|
|
context,
|
|
|
|
api
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|> add_root_errors(api, result)
|
|
|
|
|> modify_resolution(modify, modify_args)
|
|
|
|
end
|
|
|
|
end
|
2021-09-13 08:39:48 +12:00
|
|
|
|
2023-02-16 02:20:35 +13:00
|
|
|
{:error, error} ->
|
|
|
|
{:error, error}
|
2021-09-13 08:39:48 +12:00
|
|
|
end
|
2021-05-05 05:01:15 +12:00
|
|
|
rescue
|
|
|
|
e ->
|
2022-08-31 13:08:16 +12:00
|
|
|
if AshGraphql.Api.Info.show_raised_errors?(api) do
|
2021-09-10 03:49:36 +12:00
|
|
|
error = Ash.Error.to_ash_error([e], __STACKTRACE__)
|
2022-10-12 17:51:01 +13:00
|
|
|
Absinthe.Resolution.put_result(resolution, to_resolution({:error, error}, context, api))
|
2021-09-10 03:49:36 +12:00
|
|
|
else
|
2023-01-04 03:14:08 +13:00
|
|
|
something_went_wrong(resolution, e, api, __STACKTRACE__)
|
2021-09-10 03:49:36 +12:00
|
|
|
end
|
2021-04-04 19:10:50 +12:00
|
|
|
end
|
|
|
|
|
|
|
|
def resolve(
|
|
|
|
%{arguments: args, context: context} = resolution,
|
2022-11-23 07:27:53 +13:00
|
|
|
{api, resource,
|
2023-01-29 06:32:21 +13:00
|
|
|
%{name: query_name, type: :read_one, action: action, modify_resolution: modify} =
|
|
|
|
gql_query}
|
2021-04-04 19:10:50 +12:00
|
|
|
) do
|
2022-11-23 07:27:53 +13:00
|
|
|
metadata = %{
|
|
|
|
api: api,
|
|
|
|
resource: resource,
|
|
|
|
resource_short_name: Ash.Resource.Info.short_name(resource),
|
2021-04-04 19:10:50 +12:00
|
|
|
actor: Map.get(context, :actor),
|
2022-11-23 07:27:53 +13:00
|
|
|
tenant: Map.get(context, :tenant),
|
2021-04-04 19:10:50 +12:00
|
|
|
action: action,
|
2022-11-23 07:27:53 +13:00
|
|
|
source: :graphql,
|
|
|
|
query: query_name,
|
|
|
|
authorize?: AshGraphql.Api.Info.authorize?(api)
|
|
|
|
}
|
2021-04-04 19:10:50 +12:00
|
|
|
|
2023-02-16 02:20:35 +13:00
|
|
|
case handle_arguments(resource, action, args) do
|
|
|
|
{:ok, args} ->
|
|
|
|
trace api,
|
|
|
|
resource,
|
|
|
|
:gql_query,
|
|
|
|
query_name,
|
|
|
|
metadata do
|
|
|
|
opts = [
|
|
|
|
actor: Map.get(context, :actor),
|
|
|
|
action: action,
|
|
|
|
verbose?: AshGraphql.Api.Info.debug?(api)
|
|
|
|
]
|
2022-09-21 15:07:30 +12:00
|
|
|
|
2023-02-16 02:20:35 +13:00
|
|
|
query =
|
|
|
|
case Map.fetch(args, :filter) do
|
|
|
|
{:ok, filter} when filter != %{} ->
|
|
|
|
Ash.Query.do_filter(resource, filter)
|
2021-04-04 19:10:50 +12:00
|
|
|
|
2023-02-16 02:20:35 +13:00
|
|
|
_ ->
|
|
|
|
Ash.Query.new(resource)
|
|
|
|
end
|
2021-04-04 19:10:50 +12:00
|
|
|
|
2023-02-16 02:20:35 +13:00
|
|
|
query =
|
|
|
|
query
|
|
|
|
|> Ash.Query.set_tenant(Map.get(context, :tenant))
|
|
|
|
|> Ash.Query.set_context(Map.get(context, :ash_context) || %{})
|
|
|
|
|> set_query_arguments(action, args)
|
|
|
|
|> select_fields(resource, resolution)
|
|
|
|
|
|
|
|
{result, modify_args} =
|
|
|
|
case load_fields(query, resource, api, resolution) do
|
|
|
|
{:ok, query} ->
|
|
|
|
result =
|
|
|
|
query
|
|
|
|
|> Ash.Query.for_read(action, %{},
|
|
|
|
actor: opts[:actor],
|
|
|
|
authorize?: AshGraphql.Api.Info.authorize?(api)
|
|
|
|
)
|
|
|
|
|> api.read_one(opts)
|
|
|
|
|
|
|
|
{result, [query, result]}
|
2022-11-23 07:27:53 +13:00
|
|
|
|
2023-02-16 02:20:35 +13:00
|
|
|
{:error, error} ->
|
|
|
|
{{:error, error}, [query, {:error, error}]}
|
|
|
|
end
|
|
|
|
|
|
|
|
result =
|
|
|
|
add_read_metadata(result, gql_query, Ash.Resource.Info.action(query.resource, action))
|
|
|
|
|
|
|
|
resolution
|
|
|
|
|> Absinthe.Resolution.put_result(to_resolution(result, context, api))
|
|
|
|
|> add_root_errors(api, result)
|
|
|
|
|> modify_resolution(modify, modify_args)
|
|
|
|
end
|
2023-01-29 06:32:21 +13:00
|
|
|
|
2023-02-16 02:20:35 +13:00
|
|
|
{:error, error} ->
|
|
|
|
{:error, error}
|
2022-11-23 07:27:53 +13:00
|
|
|
end
|
2021-05-05 05:01:15 +12:00
|
|
|
rescue
|
|
|
|
e ->
|
2022-08-31 13:08:16 +12:00
|
|
|
if AshGraphql.Api.Info.show_raised_errors?(api) do
|
2021-09-10 03:49:36 +12:00
|
|
|
error = Ash.Error.to_ash_error([e], __STACKTRACE__)
|
2022-10-12 17:51:01 +13:00
|
|
|
Absinthe.Resolution.put_result(resolution, to_resolution({:error, error}, context, api))
|
2021-09-10 03:49:36 +12:00
|
|
|
else
|
2023-01-04 03:14:08 +13:00
|
|
|
something_went_wrong(resolution, e, api, __STACKTRACE__)
|
2021-09-10 03:49:36 +12:00
|
|
|
end
|
2020-05-02 10:35:12 +12:00
|
|
|
end
|
|
|
|
|
|
|
|
def resolve(
|
2021-04-29 08:54:06 +12:00
|
|
|
%{arguments: args, context: context} = resolution,
|
2022-11-23 07:27:53 +13:00
|
|
|
{api, resource,
|
|
|
|
%{
|
|
|
|
name: query_name,
|
|
|
|
type: :list,
|
|
|
|
relay?: relay?,
|
|
|
|
action: action,
|
|
|
|
modify_resolution: modify
|
2023-01-29 06:32:21 +13:00
|
|
|
} = gql_query}
|
2020-05-02 10:35:12 +12:00
|
|
|
) do
|
2023-02-16 02:20:35 +13:00
|
|
|
case handle_arguments(resource, action, args) do
|
|
|
|
{:ok, args} ->
|
|
|
|
metadata = %{
|
|
|
|
api: api,
|
|
|
|
resource: resource,
|
|
|
|
resource_short_name: Ash.Resource.Info.short_name(resource),
|
|
|
|
actor: Map.get(context, :actor),
|
|
|
|
tenant: Map.get(context, :tenant),
|
|
|
|
action: action,
|
|
|
|
source: :graphql,
|
|
|
|
query: query_name,
|
|
|
|
authorize?: AshGraphql.Api.Info.authorize?(api)
|
|
|
|
}
|
2020-06-06 06:41:00 +12:00
|
|
|
|
2023-02-16 02:20:35 +13:00
|
|
|
trace api,
|
|
|
|
resource,
|
|
|
|
:gql_query,
|
|
|
|
query_name,
|
|
|
|
metadata do
|
|
|
|
opts = [
|
|
|
|
actor: Map.get(context, :actor),
|
|
|
|
action: action,
|
|
|
|
verbose?: AshGraphql.Api.Info.debug?(api)
|
|
|
|
]
|
|
|
|
|
|
|
|
pagination = Ash.Resource.Info.action(resource, action).pagination
|
2023-03-18 07:55:55 +13:00
|
|
|
query = apply_load_arguments(args, Ash.Query.new(resource))
|
2023-02-16 02:20:35 +13:00
|
|
|
|
|
|
|
{result, modify_args} =
|
|
|
|
with {:ok, opts} <- validate_resolve_opts(resolution, pagination, opts, args),
|
|
|
|
result_fields <- get_result_fields(pagination, relay?),
|
|
|
|
initial_query <-
|
|
|
|
query
|
|
|
|
|> Ash.Query.set_tenant(Map.get(context, :tenant))
|
|
|
|
|> Ash.Query.set_context(Map.get(context, :ash_context) || %{})
|
|
|
|
|> set_query_arguments(action, args)
|
|
|
|
|> select_fields(resource, resolution, result_fields),
|
|
|
|
{:ok, query} <-
|
|
|
|
load_fields(initial_query, resource, api, resolution, result_fields),
|
|
|
|
{:ok, page} <-
|
|
|
|
query
|
|
|
|
|> Ash.Query.for_read(action, %{},
|
|
|
|
actor: Map.get(context, :actor),
|
|
|
|
authorize?: AshGraphql.Api.Info.authorize?(api)
|
|
|
|
)
|
|
|
|
|> api.read(opts) do
|
|
|
|
result = paginate(resource, action, page, relay?)
|
|
|
|
{result, [query, result]}
|
|
|
|
else
|
|
|
|
{:error, error} ->
|
|
|
|
{{:error, error}, [query, {:error, error}]}
|
|
|
|
end
|
|
|
|
|
|
|
|
result =
|
|
|
|
add_read_metadata(result, gql_query, Ash.Resource.Info.action(query.resource, action))
|
2022-11-23 07:27:53 +13:00
|
|
|
|
2023-02-16 02:20:35 +13:00
|
|
|
resolution
|
|
|
|
|> Absinthe.Resolution.put_result(to_resolution(result, context, api))
|
|
|
|
|> add_root_errors(api, modify_args)
|
|
|
|
|> modify_resolution(modify, modify_args)
|
|
|
|
end
|
2023-01-29 06:32:21 +13:00
|
|
|
|
2023-02-16 02:20:35 +13:00
|
|
|
{:error, error} ->
|
|
|
|
{:error, error}
|
2022-11-23 07:27:53 +13:00
|
|
|
end
|
2021-05-05 05:01:15 +12:00
|
|
|
rescue
|
|
|
|
e ->
|
2022-08-31 13:08:16 +12:00
|
|
|
if AshGraphql.Api.Info.show_raised_errors?(api) do
|
2021-09-10 03:49:36 +12:00
|
|
|
error = Ash.Error.to_ash_error([e], __STACKTRACE__)
|
2022-10-12 17:51:01 +13:00
|
|
|
Absinthe.Resolution.put_result(resolution, to_resolution({:error, error}, context, api))
|
2021-09-10 03:49:36 +12:00
|
|
|
else
|
2023-01-04 03:14:08 +13:00
|
|
|
something_went_wrong(resolution, e, api, __STACKTRACE__)
|
2021-09-10 03:49:36 +12:00
|
|
|
end
|
2020-05-02 10:35:12 +12:00
|
|
|
end
|
|
|
|
|
2023-02-23 10:25:21 +13:00
|
|
|
defp handle_arguments(_resource, nil, argument_values) do
|
|
|
|
{:ok, argument_values}
|
|
|
|
end
|
|
|
|
|
|
|
|
defp handle_arguments(resource, action, argument_values) when is_atom(action) do
|
|
|
|
action = Ash.Resource.Info.action(resource, action)
|
|
|
|
handle_arguments(resource, action, argument_values)
|
|
|
|
end
|
|
|
|
|
2023-02-16 02:20:35 +13:00
|
|
|
defp handle_arguments(resource, action, argument_values) do
|
2023-02-23 10:25:21 +13:00
|
|
|
action_arguments = action.arguments
|
2023-02-16 02:20:35 +13:00
|
|
|
|
|
|
|
attributes =
|
|
|
|
resource
|
|
|
|
|> Ash.Resource.Info.attributes()
|
|
|
|
|
|
|
|
argument_values
|
|
|
|
|> Enum.reduce_while({:ok, %{}}, fn {key, value}, {:ok, arguments} ->
|
|
|
|
argument =
|
|
|
|
Enum.find(action_arguments, &(&1.name == key)) || Enum.find(attributes, &(&1.name == key))
|
|
|
|
|
|
|
|
if argument do
|
2023-02-17 17:33:54 +13:00
|
|
|
%{type: type, name: name, constraints: constraints} = argument
|
2023-02-16 02:20:35 +13:00
|
|
|
|
2023-02-17 17:33:54 +13:00
|
|
|
case handle_argument(type, constraints, value, name) do
|
2023-02-16 02:20:35 +13:00
|
|
|
{:ok, value} ->
|
|
|
|
{:cont, {:ok, Map.put(arguments, name, value)}}
|
|
|
|
|
|
|
|
{:error, error} ->
|
|
|
|
{:halt, {:error, error}}
|
|
|
|
end
|
|
|
|
else
|
|
|
|
{:cont, {:ok, Map.put(arguments, key, value)}}
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
2023-02-17 17:33:54 +13:00
|
|
|
defp handle_argument({:array, type}, constraints, value, name) when is_list(value) do
|
|
|
|
value
|
|
|
|
|> Enum.reduce_while({:ok, []}, fn value, {:ok, acc} ->
|
|
|
|
case handle_argument(type, constraints[:items], value, name) do
|
|
|
|
{:ok, value} ->
|
|
|
|
{:cont, {:ok, [value | acc]}}
|
|
|
|
|
|
|
|
{:error, error} ->
|
|
|
|
{:halt, {:error, error}}
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
|> case do
|
|
|
|
{:ok, value} -> {:ok, Enum.reverse(value)}
|
|
|
|
{:error, error} -> {:error, error}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
defp handle_argument(Ash.Type.Union, constraints, value, name) do
|
|
|
|
handle_union_type(value, constraints, name)
|
|
|
|
end
|
|
|
|
|
|
|
|
defp handle_argument(type, constraints, value, name) do
|
2023-03-14 11:32:36 +13:00
|
|
|
cond do
|
|
|
|
Ash.Type.NewType.new_type?(type) ->
|
|
|
|
handle_argument(
|
|
|
|
Ash.Type.NewType.subtype_of(type),
|
|
|
|
Ash.Type.NewType.constraints(type, constraints),
|
|
|
|
value,
|
|
|
|
name
|
|
|
|
)
|
|
|
|
|
|
|
|
Ash.Type.embedded_type?(type) and is_map(value) ->
|
|
|
|
create_action =
|
|
|
|
if constraints[:create_action] do
|
|
|
|
Ash.Resource.Info.action(type, constraints[:create_action]) ||
|
|
|
|
Ash.Resource.Info.primary_action(type, :create)
|
|
|
|
else
|
2023-02-17 17:33:54 +13:00
|
|
|
Ash.Resource.Info.primary_action(type, :create)
|
2023-03-14 11:32:36 +13:00
|
|
|
end
|
2023-02-17 17:33:54 +13:00
|
|
|
|
2023-03-14 11:32:36 +13:00
|
|
|
update_action =
|
|
|
|
if constraints[:update_action] do
|
|
|
|
Ash.Resource.Info.action(type, constraints[:update_action]) ||
|
|
|
|
Ash.Resource.Info.primary_action(type, :update)
|
|
|
|
else
|
2023-02-17 17:33:54 +13:00
|
|
|
Ash.Resource.Info.primary_action(type, :update)
|
2023-03-14 11:32:36 +13:00
|
|
|
end
|
2023-02-17 17:33:54 +13:00
|
|
|
|
2023-03-14 11:32:36 +13:00
|
|
|
attributes = Ash.Resource.Info.public_attributes(type)
|
2023-02-17 17:33:54 +13:00
|
|
|
|
2023-03-14 11:32:36 +13:00
|
|
|
fields =
|
|
|
|
cond do
|
|
|
|
create_action && update_action ->
|
|
|
|
create_action.arguments ++ update_action.arguments ++ attributes
|
2023-02-17 17:33:54 +13:00
|
|
|
|
2023-03-14 11:32:36 +13:00
|
|
|
update_action ->
|
|
|
|
update_action.arguments ++ attributes
|
2023-02-17 17:33:54 +13:00
|
|
|
|
2023-03-14 11:32:36 +13:00
|
|
|
create_action ->
|
|
|
|
create_action.arguments ++ attributes
|
2023-02-17 17:33:54 +13:00
|
|
|
|
2023-03-14 11:32:36 +13:00
|
|
|
true ->
|
|
|
|
attributes
|
|
|
|
end
|
2023-02-17 17:33:54 +13:00
|
|
|
|
2023-03-22 09:15:25 +13:00
|
|
|
value
|
|
|
|
|> Enum.reduce_while({:ok, %{}}, fn {key, value}, {:ok, acc} ->
|
|
|
|
field =
|
|
|
|
Enum.find(fields, fn field ->
|
|
|
|
field.name == key
|
|
|
|
end)
|
|
|
|
|
|
|
|
if field do
|
|
|
|
case handle_argument(field.type, field.constraints, value, "#{name}.#{key}") do
|
|
|
|
{:ok, value} ->
|
|
|
|
{:cont, {:ok, Map.put(acc, key, value)}}
|
|
|
|
|
|
|
|
{:error, error} ->
|
|
|
|
{:halt, {:error, error}}
|
|
|
|
end
|
|
|
|
else
|
|
|
|
{:cont, {:ok, Map.put(acc, key, value)}}
|
|
|
|
end
|
|
|
|
end)
|
2023-03-14 11:32:36 +13:00
|
|
|
|
|
|
|
true ->
|
|
|
|
{:ok, value}
|
2023-02-16 02:20:35 +13:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-02-17 17:33:54 +13:00
|
|
|
defp handle_union_type(value, constraints, name) do
|
2023-02-16 02:20:35 +13:00
|
|
|
value
|
|
|
|
|> Enum.reject(fn {_key, value} ->
|
|
|
|
is_nil(value)
|
|
|
|
end)
|
|
|
|
|> case do
|
|
|
|
[] ->
|
|
|
|
{:ok, nil}
|
|
|
|
|
2023-02-17 17:33:54 +13:00
|
|
|
[{key, value}] ->
|
|
|
|
config = constraints[:types][key]
|
|
|
|
|
|
|
|
if config[:tag] && is_map(value) do
|
|
|
|
{:ok, Map.put_new(value, config[:tag], config[:tag_value])}
|
|
|
|
else
|
|
|
|
{:ok, value}
|
|
|
|
end
|
2023-02-16 02:20:35 +13:00
|
|
|
|
|
|
|
key_vals ->
|
|
|
|
keys = Enum.map_join(key_vals, ", ", fn {key, _} -> to_string(key) end)
|
|
|
|
|
|
|
|
{:error,
|
|
|
|
%{message: "Only one key can be specified, but got #{keys}", fields: ["#{name}"]}}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-09-21 15:07:30 +12:00
|
|
|
def validate_resolve_opts(resolution, pagination, opts, args) do
|
2023-03-21 03:10:49 +13:00
|
|
|
if pagination && (pagination.offset? || pagination.keyset?) do
|
|
|
|
with page_opts <-
|
|
|
|
args
|
|
|
|
|> Map.take([:limit, :offset, :first, :after, :before, :last])
|
|
|
|
|> Enum.reject(fn {_, val} -> is_nil(val) end),
|
|
|
|
{:ok, page_opts} <- validate_offset_opts(page_opts, pagination),
|
|
|
|
{:ok, page_opts} <- validate_keyset_opts(page_opts, pagination) do
|
|
|
|
field_names = resolution |> fields([]) |> names_only()
|
|
|
|
|
|
|
|
page =
|
|
|
|
if Enum.any?(field_names, &(&1 == :count)) do
|
|
|
|
Keyword.put(page_opts, :count, true)
|
|
|
|
else
|
|
|
|
page_opts
|
|
|
|
end
|
2022-10-22 01:51:31 +13:00
|
|
|
|
2023-03-21 03:10:49 +13:00
|
|
|
{:ok, Keyword.put(opts, :page, page)}
|
|
|
|
else
|
|
|
|
error ->
|
|
|
|
error
|
|
|
|
end
|
2022-10-22 01:51:31 +13:00
|
|
|
else
|
2023-03-21 03:10:49 +13:00
|
|
|
{:ok, opts}
|
2022-09-21 15:07:30 +12:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-10-22 01:51:31 +13:00
|
|
|
defp validate_offset_opts(opts, %{offset?: true, max_page_size: max_page_size}) do
|
2022-09-21 15:07:30 +12:00
|
|
|
limit =
|
|
|
|
case opts |> Keyword.take([:limit]) |> Enum.into(%{}) do
|
|
|
|
%{limit: limit} ->
|
|
|
|
min(limit, max_page_size)
|
|
|
|
|
|
|
|
_ ->
|
|
|
|
max_page_size
|
|
|
|
end
|
|
|
|
|
|
|
|
{:ok, Keyword.put(opts, :limit, limit)}
|
|
|
|
end
|
|
|
|
|
2022-10-22 01:51:31 +13:00
|
|
|
defp validate_offset_opts(opts, _) do
|
|
|
|
{:ok, opts}
|
|
|
|
end
|
|
|
|
|
|
|
|
defp validate_keyset_opts(opts, %{keyset?: true, max_page_size: max_page_size}) do
|
2022-09-21 15:07:30 +12:00
|
|
|
case opts |> Keyword.take([:first, :last, :after, :before]) |> Enum.into(%{}) do
|
|
|
|
%{first: _first, last: _last} ->
|
|
|
|
{:error,
|
|
|
|
%Ash.Error.Query.InvalidQuery{
|
|
|
|
message: "You can pass either `first` or `last`, not both",
|
|
|
|
field: :first
|
|
|
|
}}
|
|
|
|
|
|
|
|
%{first: _first, before: _before} ->
|
|
|
|
{:error,
|
|
|
|
%Ash.Error.Query.InvalidQuery{
|
|
|
|
message:
|
|
|
|
"You can pass either `first` and `after` cursor, or `last` and `before` cursor",
|
|
|
|
field: :first
|
|
|
|
}}
|
|
|
|
|
|
|
|
%{last: _last, after: _after} ->
|
|
|
|
{:error,
|
|
|
|
%Ash.Error.Query.InvalidQuery{
|
|
|
|
message:
|
|
|
|
"You can pass either `first` and `after` cursor, or `last` and `before` cursor",
|
|
|
|
field: :last
|
|
|
|
}}
|
|
|
|
|
|
|
|
%{first: first} ->
|
|
|
|
{:ok, opts |> Keyword.delete(:first) |> Keyword.put(:limit, min(first, max_page_size))}
|
|
|
|
|
|
|
|
%{last: last, before: before} when not is_nil(before) ->
|
|
|
|
{:ok, opts |> Keyword.delete(:last) |> Keyword.put(:limit, min(last, max_page_size))}
|
|
|
|
|
|
|
|
%{last: _last} ->
|
|
|
|
{:error,
|
|
|
|
%Ash.Error.Query.InvalidQuery{
|
|
|
|
message: "You can pass `last` only with `before` cursor",
|
|
|
|
field: :last
|
|
|
|
}}
|
|
|
|
|
|
|
|
_ ->
|
|
|
|
{:ok, Keyword.put(opts, :limit, max_page_size)}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-10-22 01:51:31 +13:00
|
|
|
defp validate_keyset_opts(opts, _) do
|
2022-09-21 15:07:30 +12:00
|
|
|
{:ok, opts}
|
|
|
|
end
|
|
|
|
|
2022-10-13 05:11:51 +13:00
|
|
|
defp get_result_fields(%{keyset?: true}, true) do
|
2022-09-21 15:07:30 +12:00
|
|
|
["edges", "node"]
|
|
|
|
end
|
|
|
|
|
2022-10-13 05:11:51 +13:00
|
|
|
defp get_result_fields(%{keyset?: true}, false) do
|
2022-09-21 15:07:30 +12:00
|
|
|
["results"]
|
|
|
|
end
|
|
|
|
|
2022-10-13 05:11:51 +13:00
|
|
|
defp get_result_fields(%{offset?: true}, _) do
|
|
|
|
["results"]
|
|
|
|
end
|
|
|
|
|
|
|
|
defp get_result_fields(_pagination, _) do
|
2022-09-21 15:07:30 +12:00
|
|
|
[]
|
|
|
|
end
|
|
|
|
|
2022-10-13 05:11:51 +13:00
|
|
|
defp paginate(
|
|
|
|
_resource,
|
|
|
|
_action,
|
|
|
|
%Ash.Page.Keyset{
|
|
|
|
results: results,
|
|
|
|
more?: more,
|
|
|
|
after: after_cursor,
|
2022-10-21 10:51:10 +13:00
|
|
|
before: before_cursor,
|
|
|
|
count: count
|
2022-10-13 05:11:51 +13:00
|
|
|
},
|
2022-10-21 10:51:10 +13:00
|
|
|
relay?
|
2022-10-13 05:11:51 +13:00
|
|
|
) do
|
2022-09-21 15:07:30 +12:00
|
|
|
{start_cursor, end_cursor} =
|
|
|
|
case results do
|
|
|
|
[] ->
|
|
|
|
{nil, nil}
|
|
|
|
|
|
|
|
[first] ->
|
|
|
|
{first.__metadata__.keyset, first.__metadata__.keyset}
|
|
|
|
|
|
|
|
[first | rest] ->
|
|
|
|
last = List.last(rest)
|
|
|
|
{first.__metadata__.keyset, last.__metadata__.keyset}
|
|
|
|
end
|
|
|
|
|
|
|
|
{has_previous_page, has_next_page} =
|
|
|
|
case {after_cursor, before_cursor} do
|
|
|
|
{nil, nil} ->
|
|
|
|
{false, more}
|
|
|
|
|
|
|
|
{_, nil} ->
|
|
|
|
{true, more}
|
|
|
|
|
|
|
|
{nil, _} ->
|
|
|
|
# https://github.com/ash-project/ash_graphql/pull/36#issuecomment-1243892511
|
|
|
|
{more, not Enum.empty?(results)}
|
|
|
|
end
|
|
|
|
|
2022-10-21 10:51:10 +13:00
|
|
|
if relay? do
|
|
|
|
{
|
|
|
|
:ok,
|
|
|
|
%{
|
|
|
|
page_info: %{
|
|
|
|
start_cursor: start_cursor,
|
|
|
|
end_cursor: end_cursor,
|
|
|
|
has_next_page: has_next_page,
|
|
|
|
has_previous_page: has_previous_page
|
|
|
|
},
|
|
|
|
count: count,
|
|
|
|
edges:
|
|
|
|
Enum.map(results, fn result ->
|
|
|
|
%{
|
|
|
|
cursor: result.__metadata__.keyset,
|
|
|
|
node: result
|
|
|
|
}
|
|
|
|
end)
|
|
|
|
}
|
2022-09-21 15:07:30 +12:00
|
|
|
}
|
2022-10-21 10:51:10 +13:00
|
|
|
else
|
|
|
|
{:ok, %{results: results, count: count, start_keyset: start_cursor, end_keyset: end_cursor}}
|
|
|
|
end
|
2022-10-13 05:11:51 +13:00
|
|
|
end
|
|
|
|
|
2022-10-22 01:51:31 +13:00
|
|
|
defp paginate(
|
|
|
|
_resource,
|
|
|
|
_action,
|
|
|
|
%Ash.Page.Offset{results: results, count: count, more?: more},
|
|
|
|
true
|
|
|
|
) do
|
|
|
|
{start_cursor, end_cursor} =
|
|
|
|
case results do
|
|
|
|
[] ->
|
|
|
|
{nil, nil}
|
|
|
|
|
|
|
|
[first] ->
|
|
|
|
{first.__metadata__.keyset, first.__metadata__.keyset}
|
|
|
|
|
|
|
|
[first | rest] ->
|
|
|
|
last = List.last(rest)
|
|
|
|
{first.__metadata__.keyset, last.__metadata__.keyset}
|
|
|
|
end
|
|
|
|
|
|
|
|
has_previous_page = false
|
|
|
|
has_next_page = more
|
|
|
|
|
|
|
|
{
|
|
|
|
:ok,
|
|
|
|
%{
|
|
|
|
page_info: %{
|
|
|
|
start_cursor: start_cursor,
|
|
|
|
end_cursor: end_cursor,
|
|
|
|
has_next_page: has_next_page,
|
|
|
|
has_previous_page: has_previous_page
|
|
|
|
},
|
|
|
|
count: count,
|
|
|
|
edges:
|
|
|
|
Enum.map(results, fn result ->
|
|
|
|
%{
|
|
|
|
cursor: result.__metadata__.keyset,
|
|
|
|
node: result
|
|
|
|
}
|
|
|
|
end)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
2023-02-09 05:16:48 +13:00
|
|
|
defp paginate(
|
|
|
|
_resource,
|
|
|
|
_action,
|
|
|
|
%Ash.Page.Offset{results: results, count: count, more?: more?},
|
|
|
|
_
|
|
|
|
) do
|
|
|
|
{:ok, %{results: results, count: count, more?: more?}}
|
2022-09-21 15:07:30 +12:00
|
|
|
end
|
|
|
|
|
2022-10-13 05:11:51 +13:00
|
|
|
defp paginate(resource, action, page, relay?) do
|
2022-09-21 15:07:30 +12:00
|
|
|
case Ash.Resource.Info.action(resource, action).pagination do
|
|
|
|
%{offset?: true} ->
|
2022-10-13 05:11:51 +13:00
|
|
|
paginate(
|
|
|
|
resource,
|
|
|
|
action,
|
2023-02-09 05:16:48 +13:00
|
|
|
%Ash.Page.Offset{results: page, count: Enum.count(page), more?: false},
|
2022-10-13 05:11:51 +13:00
|
|
|
relay?
|
|
|
|
)
|
2022-09-21 15:07:30 +12:00
|
|
|
|
|
|
|
%{keyset?: true} ->
|
2022-10-13 05:11:51 +13:00
|
|
|
paginate(
|
|
|
|
resource,
|
|
|
|
action,
|
|
|
|
%Ash.Page.Keyset{
|
|
|
|
results: page,
|
|
|
|
more?: false,
|
|
|
|
after: nil,
|
|
|
|
before: nil
|
|
|
|
},
|
|
|
|
relay?
|
|
|
|
)
|
2022-09-21 15:07:30 +12:00
|
|
|
|
|
|
|
_ ->
|
|
|
|
{:ok, page}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-02-23 07:13:42 +13:00
|
|
|
def mutate(%Absinthe.Resolution{state: :resolved} = resolution, _),
|
|
|
|
do: resolution
|
|
|
|
|
2020-08-14 09:39:59 +12:00
|
|
|
def mutate(
|
2021-05-10 18:19:47 +12:00
|
|
|
%{arguments: arguments, context: context} = resolution,
|
2021-06-15 04:08:26 +12:00
|
|
|
{api, resource,
|
2022-10-10 14:05:58 +13:00
|
|
|
%{
|
|
|
|
type: :create,
|
2022-11-23 07:27:53 +13:00
|
|
|
name: mutation_name,
|
2022-10-10 14:05:58 +13:00
|
|
|
action: action,
|
|
|
|
upsert?: upsert?,
|
|
|
|
upsert_identity: upsert_identity,
|
|
|
|
modify_resolution: modify
|
|
|
|
}}
|
2020-08-14 09:39:59 +12:00
|
|
|
) do
|
2023-02-16 02:20:35 +13:00
|
|
|
input = arguments[:input] || %{}
|
|
|
|
|
|
|
|
case handle_arguments(resource, action, input) do
|
|
|
|
{:ok, input} ->
|
|
|
|
metadata = %{
|
|
|
|
api: api,
|
|
|
|
resource: resource,
|
|
|
|
resource_short_name: Ash.Resource.Info.short_name(resource),
|
|
|
|
actor: Map.get(context, :actor),
|
|
|
|
tenant: Map.get(context, :tenant),
|
|
|
|
action: action,
|
|
|
|
source: :graphql,
|
|
|
|
mutation_name: mutation_name,
|
|
|
|
authorize?: AshGraphql.Api.Info.authorize?(api)
|
|
|
|
}
|
2020-08-14 09:39:59 +12:00
|
|
|
|
2023-02-16 02:20:35 +13:00
|
|
|
trace api,
|
|
|
|
resource,
|
|
|
|
:gql_mutation,
|
|
|
|
mutation_name,
|
|
|
|
metadata do
|
|
|
|
opts = [
|
|
|
|
actor: Map.get(context, :actor),
|
|
|
|
action: action,
|
|
|
|
verbose?: AshGraphql.Api.Info.debug?(api),
|
|
|
|
upsert?: upsert?,
|
|
|
|
after_action: fn _changeset, result ->
|
|
|
|
load_fields(result, resource, api, resolution, ["result"])
|
|
|
|
end
|
|
|
|
]
|
2020-09-24 12:54:57 +12:00
|
|
|
|
2023-02-16 02:20:35 +13:00
|
|
|
opts =
|
|
|
|
if upsert? && upsert_identity do
|
|
|
|
Keyword.put(opts, :upsert_identity, upsert_identity)
|
|
|
|
else
|
|
|
|
opts
|
|
|
|
end
|
2020-08-14 09:39:59 +12:00
|
|
|
|
2023-02-16 02:20:35 +13:00
|
|
|
changeset =
|
|
|
|
resource
|
|
|
|
|> Ash.Changeset.new()
|
|
|
|
|> Ash.Changeset.set_tenant(Map.get(context, :tenant))
|
|
|
|
|> Ash.Changeset.set_context(Map.get(context, :ash_context) || %{})
|
|
|
|
|> Ash.Changeset.for_create(action, input,
|
|
|
|
actor: Map.get(context, :actor),
|
|
|
|
authorize?: AshGraphql.Api.Info.authorize?(api)
|
|
|
|
)
|
|
|
|
|> select_fields(resource, resolution, ["result"])
|
2022-11-23 07:27:53 +13:00
|
|
|
|
2023-02-16 02:20:35 +13:00
|
|
|
{result, modify_args} =
|
|
|
|
changeset
|
|
|
|
|> api.create(opts)
|
|
|
|
|> case do
|
|
|
|
{:ok, value} ->
|
|
|
|
{{:ok, add_metadata(%{result: value, errors: []}, value, changeset.action)},
|
|
|
|
[changeset, {:ok, value}]}
|
|
|
|
|
|
|
|
{:error, %{changeset: changeset} = error} ->
|
|
|
|
{{:ok, %{result: nil, errors: to_errors(changeset.errors, context, api)}},
|
|
|
|
[changeset, {:error, error}]}
|
|
|
|
end
|
2022-11-23 07:27:53 +13:00
|
|
|
|
2023-02-16 02:20:35 +13:00
|
|
|
resolution
|
|
|
|
|> Absinthe.Resolution.put_result(to_resolution(result, context, api))
|
|
|
|
|> add_root_errors(api, modify_args)
|
|
|
|
|> modify_resolution(modify, modify_args)
|
2022-11-23 07:27:53 +13:00
|
|
|
end
|
|
|
|
|
2023-02-16 02:20:35 +13:00
|
|
|
{:error, error} ->
|
|
|
|
{:error, error}
|
2022-11-23 07:27:53 +13:00
|
|
|
end
|
2021-05-05 05:01:15 +12:00
|
|
|
rescue
|
|
|
|
e ->
|
2022-08-31 13:08:16 +12:00
|
|
|
if AshGraphql.Api.Info.show_raised_errors?(api) do
|
2021-09-10 03:49:36 +12:00
|
|
|
error = Ash.Error.to_ash_error([e], __STACKTRACE__)
|
|
|
|
|
2022-08-31 13:08:16 +12:00
|
|
|
if AshGraphql.Api.Info.root_level_errors?(api) do
|
2021-09-10 03:49:36 +12:00
|
|
|
Absinthe.Resolution.put_result(
|
|
|
|
resolution,
|
2022-10-12 17:51:01 +13:00
|
|
|
to_resolution({:error, error}, context, api)
|
2021-09-10 03:49:36 +12:00
|
|
|
)
|
|
|
|
else
|
|
|
|
Absinthe.Resolution.put_result(
|
|
|
|
resolution,
|
2022-10-12 17:51:01 +13:00
|
|
|
to_resolution(
|
|
|
|
{:ok, %{result: nil, errors: to_errors(error, context, api)}},
|
|
|
|
context,
|
|
|
|
api
|
|
|
|
)
|
2021-09-10 03:49:36 +12:00
|
|
|
)
|
|
|
|
end
|
|
|
|
else
|
2023-01-04 03:14:08 +13:00
|
|
|
something_went_wrong(resolution, e, api, __STACKTRACE__)
|
2021-09-10 03:49:36 +12:00
|
|
|
end
|
2020-08-14 09:39:59 +12:00
|
|
|
end
|
|
|
|
|
|
|
|
def mutate(
|
2021-05-10 18:19:47 +12:00
|
|
|
%{arguments: arguments, context: context} = resolution,
|
2021-04-20 07:26:20 +12:00
|
|
|
{api, resource,
|
2021-06-15 04:08:26 +12:00
|
|
|
%{
|
2022-11-23 07:27:53 +13:00
|
|
|
name: mutation_name,
|
2021-06-15 04:08:26 +12:00
|
|
|
type: :update,
|
|
|
|
action: action,
|
|
|
|
identity: identity,
|
|
|
|
read_action: read_action,
|
|
|
|
modify_resolution: modify
|
|
|
|
}}
|
2020-08-14 09:39:59 +12:00
|
|
|
) do
|
2023-02-16 02:20:35 +13:00
|
|
|
input = arguments[:input] || %{}
|
|
|
|
|
2023-02-23 10:25:21 +13:00
|
|
|
args_result =
|
|
|
|
with {:ok, input} <- handle_arguments(resource, action, input),
|
|
|
|
{:ok, read_action_input} <-
|
2023-02-23 10:36:21 +13:00
|
|
|
handle_arguments(resource, read_action, Map.delete(arguments, :input)) do
|
2023-02-23 10:25:21 +13:00
|
|
|
{:ok, input, read_action_input}
|
|
|
|
end
|
|
|
|
|
|
|
|
case args_result do
|
|
|
|
{:ok, input, read_action_input} ->
|
2023-02-16 02:20:35 +13:00
|
|
|
metadata = %{
|
|
|
|
api: api,
|
|
|
|
resource: resource,
|
|
|
|
resource_short_name: Ash.Resource.Info.short_name(resource),
|
|
|
|
actor: Map.get(context, :actor),
|
|
|
|
tenant: Map.get(context, :tenant),
|
|
|
|
action: action,
|
|
|
|
mutation: mutation_name,
|
|
|
|
source: :graphql,
|
|
|
|
authorize?: AshGraphql.Api.Info.authorize?(api)
|
|
|
|
}
|
2021-01-13 09:14:35 +13:00
|
|
|
|
2023-02-16 02:20:35 +13:00
|
|
|
trace api,
|
|
|
|
resource,
|
|
|
|
:gql_mutation,
|
|
|
|
mutation_name,
|
|
|
|
metadata do
|
|
|
|
filter = identity_filter(identity, resource, arguments)
|
2021-01-13 09:14:35 +13:00
|
|
|
|
2023-02-16 02:20:35 +13:00
|
|
|
case filter do
|
|
|
|
{:ok, filter} ->
|
|
|
|
resource
|
|
|
|
|> Ash.Query.do_filter(filter)
|
|
|
|
|> Ash.Query.set_tenant(Map.get(context, :tenant))
|
|
|
|
|> Ash.Query.set_context(Map.get(context, :ash_context) || %{})
|
2023-02-23 10:25:21 +13:00
|
|
|
|> set_query_arguments(action, read_action_input)
|
2023-02-23 10:40:20 +13:00
|
|
|
|> api.read_one(
|
2023-02-16 02:20:35 +13:00
|
|
|
action: read_action,
|
2022-11-23 07:27:53 +13:00
|
|
|
verbose?: AshGraphql.Api.Info.debug?(api),
|
2023-02-16 02:20:35 +13:00
|
|
|
actor: Map.get(context, :actor),
|
|
|
|
authorize?: AshGraphql.Api.Info.authorize?(api)
|
|
|
|
)
|
|
|
|
|> case do
|
2023-02-23 10:40:20 +13:00
|
|
|
{:ok, nil} ->
|
2023-02-16 02:20:35 +13:00
|
|
|
result = not_found(filter, resource, context, api)
|
|
|
|
|
|
|
|
resolution
|
|
|
|
|> Absinthe.Resolution.put_result(result)
|
|
|
|
|> add_root_errors(api, result)
|
|
|
|
|
2023-02-23 10:40:20 +13:00
|
|
|
{:ok, initial} ->
|
2023-02-16 02:20:35 +13:00
|
|
|
opts = [
|
|
|
|
actor: Map.get(context, :actor),
|
|
|
|
action: action,
|
|
|
|
verbose?: AshGraphql.Api.Info.debug?(api),
|
|
|
|
after_action: fn _changeset, result ->
|
|
|
|
load_fields(result, resource, api, resolution, ["result"])
|
|
|
|
end
|
|
|
|
]
|
|
|
|
|
|
|
|
changeset =
|
|
|
|
initial
|
|
|
|
|> Ash.Changeset.new()
|
|
|
|
|> Ash.Changeset.set_tenant(Map.get(context, :tenant))
|
|
|
|
|> Ash.Changeset.set_context(Map.get(context, :ash_context) || %{})
|
|
|
|
|> Ash.Changeset.for_update(action, input,
|
|
|
|
actor: Map.get(context, :actor),
|
|
|
|
authorize?: AshGraphql.Api.Info.authorize?(api)
|
|
|
|
)
|
|
|
|
|> select_fields(resource, resolution, ["result"])
|
|
|
|
|
|
|
|
{result, modify_args} =
|
|
|
|
changeset
|
|
|
|
|> api.update(opts)
|
|
|
|
|> case do
|
|
|
|
{:ok, value} ->
|
|
|
|
{{:ok,
|
|
|
|
add_metadata(%{result: value, errors: []}, value, changeset.action)},
|
|
|
|
[changeset, {:ok, value}]}
|
|
|
|
|
|
|
|
{:error, error} ->
|
|
|
|
{{:ok, %{result: nil, errors: to_errors(List.wrap(error), context, api)}},
|
|
|
|
[changeset, {:error, error}]}
|
|
|
|
end
|
|
|
|
|
|
|
|
resolution
|
|
|
|
|> Absinthe.Resolution.put_result(to_resolution(result, context, api))
|
|
|
|
|> add_root_errors(api, modify_args)
|
|
|
|
|> modify_resolution(modify, modify_args)
|
2023-02-23 10:40:20 +13:00
|
|
|
|
|
|
|
{:error, error} ->
|
|
|
|
Absinthe.Resolution.put_result(
|
|
|
|
resolution,
|
|
|
|
to_resolution({:error, error}, context, api)
|
|
|
|
)
|
2023-02-16 02:20:35 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
{:error, error} ->
|
|
|
|
Absinthe.Resolution.put_result(
|
|
|
|
resolution,
|
|
|
|
to_resolution({:error, error}, context, api)
|
|
|
|
)
|
2022-11-23 07:27:53 +13:00
|
|
|
end
|
2023-02-16 02:20:35 +13:00
|
|
|
end
|
2022-11-23 07:27:53 +13:00
|
|
|
|
2023-02-16 02:20:35 +13:00
|
|
|
{:error, error} ->
|
|
|
|
{:error, error}
|
2020-08-14 09:39:59 +12:00
|
|
|
end
|
2021-05-05 05:01:15 +12:00
|
|
|
rescue
|
|
|
|
e ->
|
2022-08-31 13:08:16 +12:00
|
|
|
if AshGraphql.Api.Info.show_raised_errors?(api) do
|
2021-09-10 03:49:36 +12:00
|
|
|
error = Ash.Error.to_ash_error([e], __STACKTRACE__)
|
|
|
|
|
2022-08-31 13:08:16 +12:00
|
|
|
if AshGraphql.Api.Info.root_level_errors?(api) do
|
2021-09-10 03:49:36 +12:00
|
|
|
Absinthe.Resolution.put_result(
|
|
|
|
resolution,
|
2022-10-12 17:51:01 +13:00
|
|
|
to_resolution({:error, error}, context, api)
|
2021-09-10 03:49:36 +12:00
|
|
|
)
|
|
|
|
else
|
|
|
|
Absinthe.Resolution.put_result(
|
|
|
|
resolution,
|
2022-10-12 17:51:01 +13:00
|
|
|
to_resolution(
|
|
|
|
{:ok, %{result: nil, errors: to_errors(error, context, api)}},
|
|
|
|
context,
|
|
|
|
api
|
|
|
|
)
|
2021-09-10 03:49:36 +12:00
|
|
|
)
|
|
|
|
end
|
|
|
|
else
|
2023-01-04 03:14:08 +13:00
|
|
|
something_went_wrong(resolution, e, api, __STACKTRACE__)
|
2021-09-10 03:49:36 +12:00
|
|
|
end
|
2020-08-14 09:39:59 +12:00
|
|
|
end
|
|
|
|
|
2020-10-28 19:16:16 +13:00
|
|
|
def mutate(
|
2020-11-18 20:14:33 +13:00
|
|
|
%{arguments: arguments, context: context} = resolution,
|
2021-04-20 07:26:20 +12:00
|
|
|
{api, resource,
|
2021-06-15 04:08:26 +12:00
|
|
|
%{
|
2022-11-23 07:27:53 +13:00
|
|
|
name: mutation_name,
|
2021-06-15 04:08:26 +12:00
|
|
|
type: :destroy,
|
|
|
|
action: action,
|
|
|
|
identity: identity,
|
|
|
|
read_action: read_action,
|
|
|
|
modify_resolution: modify
|
|
|
|
}}
|
2020-10-28 19:16:16 +13:00
|
|
|
) do
|
2023-02-16 02:20:35 +13:00
|
|
|
input = arguments[:input] || %{}
|
|
|
|
|
2023-02-23 10:25:21 +13:00
|
|
|
args_result =
|
|
|
|
with {:ok, input} <- handle_arguments(resource, action, input),
|
|
|
|
{:ok, read_action_input} <-
|
2023-02-23 10:36:21 +13:00
|
|
|
handle_arguments(resource, read_action, Map.delete(arguments, :input)) do
|
2023-02-23 10:25:21 +13:00
|
|
|
{:ok, input, read_action_input}
|
|
|
|
end
|
|
|
|
|
|
|
|
case args_result do
|
|
|
|
{:ok, input, read_action_input} ->
|
2023-02-16 02:20:35 +13:00
|
|
|
metadata = %{
|
|
|
|
api: api,
|
|
|
|
resource: resource,
|
|
|
|
resource_short_name: Ash.Resource.Info.short_name(resource),
|
|
|
|
actor: Map.get(context, :actor),
|
|
|
|
tenant: Map.get(context, :tenant),
|
|
|
|
action: action,
|
|
|
|
source: :graphql,
|
|
|
|
mutation: mutation_name,
|
|
|
|
authorize?: AshGraphql.Api.Info.authorize?(api)
|
|
|
|
}
|
2022-11-23 07:27:53 +13:00
|
|
|
|
2023-02-16 02:20:35 +13:00
|
|
|
trace api,
|
|
|
|
resource,
|
|
|
|
:gql_mutation,
|
|
|
|
mutation_name,
|
|
|
|
metadata do
|
|
|
|
filter = identity_filter(identity, resource, arguments)
|
2022-11-23 07:27:53 +13:00
|
|
|
|
2023-02-16 02:20:35 +13:00
|
|
|
case filter do
|
|
|
|
{:ok, filter} ->
|
|
|
|
resource
|
|
|
|
|> Ash.Query.do_filter(filter)
|
|
|
|
|> Ash.Query.set_tenant(Map.get(context, :tenant))
|
|
|
|
|> Ash.Query.set_context(Map.get(context, :ash_context) || %{})
|
2023-02-23 10:25:21 +13:00
|
|
|
|> set_query_arguments(action, read_action_input)
|
2023-02-23 10:40:20 +13:00
|
|
|
|> api.read_one(
|
2023-02-16 02:20:35 +13:00
|
|
|
action: read_action,
|
|
|
|
verbose?: AshGraphql.Api.Info.debug?(api),
|
|
|
|
actor: Map.get(context, :actor),
|
|
|
|
authorize?: AshGraphql.Api.Info.authorize?(api)
|
|
|
|
)
|
|
|
|
|> case do
|
2023-02-23 10:40:20 +13:00
|
|
|
{:ok, nil} ->
|
2023-02-16 02:20:35 +13:00
|
|
|
result = not_found(filter, resource, context, api)
|
|
|
|
|
|
|
|
resolution
|
|
|
|
|> Absinthe.Resolution.put_result(result)
|
|
|
|
|> add_root_errors(api, result)
|
|
|
|
|
2023-02-23 10:40:20 +13:00
|
|
|
{:ok, initial} ->
|
2023-02-16 02:20:35 +13:00
|
|
|
opts = destroy_opts(api, context, action)
|
|
|
|
|
|
|
|
changeset =
|
|
|
|
initial
|
|
|
|
|> Ash.Changeset.new()
|
|
|
|
|> Ash.Changeset.set_tenant(Map.get(context, :tenant))
|
|
|
|
|> Ash.Changeset.set_context(Map.get(context, :ash_context) || %{})
|
|
|
|
|> Ash.Changeset.for_destroy(action, input,
|
|
|
|
actor: Map.get(context, :actor),
|
|
|
|
authorize?: AshGraphql.Api.Info.authorize?(api)
|
|
|
|
)
|
|
|
|
|> select_fields(resource, resolution, ["result"])
|
|
|
|
|
|
|
|
{result, modify_args} =
|
|
|
|
changeset
|
|
|
|
|> api.destroy(opts)
|
|
|
|
|> destroy_result(initial, resource, changeset, api, resolution)
|
|
|
|
|
|
|
|
resolution
|
|
|
|
|> Absinthe.Resolution.put_result(to_resolution(result, context, api))
|
|
|
|
|> add_root_errors(api, result)
|
|
|
|
|> modify_resolution(modify, modify_args)
|
2023-02-23 10:40:20 +13:00
|
|
|
|
|
|
|
{:error, error} ->
|
|
|
|
Absinthe.Resolution.put_result(
|
|
|
|
resolution,
|
|
|
|
to_resolution({:error, error}, context, api)
|
|
|
|
)
|
2023-02-16 02:20:35 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
{:error, error} ->
|
|
|
|
Absinthe.Resolution.put_result(
|
|
|
|
resolution,
|
|
|
|
to_resolution({:error, error}, context, api)
|
|
|
|
)
|
2022-11-23 07:27:53 +13:00
|
|
|
end
|
2023-02-16 02:20:35 +13:00
|
|
|
end
|
2021-01-13 09:14:35 +13:00
|
|
|
|
2023-02-16 02:20:35 +13:00
|
|
|
{:error, error} ->
|
|
|
|
{:error, error}
|
2021-01-13 09:14:35 +13:00
|
|
|
end
|
2021-05-05 05:01:15 +12:00
|
|
|
rescue
|
|
|
|
e ->
|
2022-08-31 13:08:16 +12:00
|
|
|
if AshGraphql.Api.Info.show_raised_errors?(api) do
|
2021-09-10 03:49:36 +12:00
|
|
|
error = Ash.Error.to_ash_error([e], __STACKTRACE__)
|
|
|
|
|
2022-08-31 13:08:16 +12:00
|
|
|
if AshGraphql.Api.Info.root_level_errors?(api) do
|
2021-09-10 03:49:36 +12:00
|
|
|
Absinthe.Resolution.put_result(
|
|
|
|
resolution,
|
2022-10-12 17:51:01 +13:00
|
|
|
to_resolution({:error, error}, context, api)
|
2021-09-10 03:49:36 +12:00
|
|
|
)
|
|
|
|
else
|
|
|
|
Absinthe.Resolution.put_result(
|
|
|
|
resolution,
|
2022-10-12 17:51:01 +13:00
|
|
|
to_resolution(
|
|
|
|
{:ok, %{result: nil, errors: to_errors(error, context, api)}},
|
|
|
|
context,
|
|
|
|
api
|
|
|
|
)
|
2021-09-10 03:49:36 +12:00
|
|
|
)
|
|
|
|
end
|
|
|
|
else
|
2023-01-04 03:14:08 +13:00
|
|
|
something_went_wrong(resolution, e, api, __STACKTRACE__)
|
2021-09-10 03:49:36 +12:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-01-04 03:14:08 +13:00
|
|
|
defp log_exception(e, stacktrace) do
|
2021-09-10 03:49:36 +12:00
|
|
|
uuid = Ash.UUID.generate()
|
|
|
|
|
|
|
|
Logger.error("""
|
|
|
|
#{uuid}: Exception raised while resolving query.
|
|
|
|
|
2023-01-04 03:14:08 +13:00
|
|
|
#{Exception.format(:error, e, stacktrace)}
|
2021-09-10 03:49:36 +12:00
|
|
|
""")
|
|
|
|
|
|
|
|
uuid
|
|
|
|
end
|
|
|
|
|
2023-01-04 03:14:08 +13:00
|
|
|
defp something_went_wrong(resolution, e, api, stacktrace) do
|
2022-12-10 17:22:42 +13:00
|
|
|
tracer = AshGraphql.Api.Info.tracer(api)
|
|
|
|
|
|
|
|
if tracer do
|
|
|
|
tracer.set_error(Ash.Error.to_ash_error(e))
|
|
|
|
end
|
|
|
|
|
2023-01-04 03:14:08 +13:00
|
|
|
uuid = log_exception(e, stacktrace)
|
2021-09-10 03:49:36 +12:00
|
|
|
|
|
|
|
Absinthe.Resolution.put_result(
|
|
|
|
resolution,
|
|
|
|
{:error,
|
|
|
|
[
|
|
|
|
%{
|
|
|
|
message: "Something went wrong. Unique error id: `#{uuid}`",
|
|
|
|
code: "something_went_wrong",
|
|
|
|
vars: %{},
|
|
|
|
fields: [],
|
|
|
|
short_message: "Something went wrong."
|
|
|
|
}
|
|
|
|
]}
|
|
|
|
)
|
2021-01-13 09:14:35 +13:00
|
|
|
end
|
2020-08-14 09:39:59 +12:00
|
|
|
|
2021-06-15 04:08:26 +12:00
|
|
|
defp modify_resolution(resolution, nil, _), do: resolution
|
|
|
|
|
|
|
|
defp modify_resolution(resolution, {m, f, a}, args) do
|
|
|
|
apply(m, f, [resolution | args] ++ a)
|
|
|
|
end
|
|
|
|
|
2021-04-20 07:26:20 +12:00
|
|
|
def identity_filter(false, _resource, _arguments) do
|
|
|
|
{:ok, nil}
|
|
|
|
end
|
|
|
|
|
|
|
|
def identity_filter(nil, resource, arguments) do
|
2022-10-13 12:02:04 +13:00
|
|
|
if AshGraphql.Resource.Info.encode_primary_key?(resource) do
|
|
|
|
case AshGraphql.Resource.decode_primary_key(resource, Map.get(arguments, :id) || "") do
|
|
|
|
{:ok, value} ->
|
|
|
|
{:ok, value}
|
2021-08-04 10:56:08 +12:00
|
|
|
|
2022-10-13 12:02:04 +13:00
|
|
|
{:error, error} ->
|
|
|
|
{:error, error}
|
|
|
|
end
|
|
|
|
else
|
|
|
|
resource
|
|
|
|
|> Ash.Resource.Info.primary_key()
|
|
|
|
|> Enum.reduce_while({:ok, nil}, fn key, {:ok, expr} ->
|
|
|
|
value = Map.get(arguments, key)
|
|
|
|
|
|
|
|
if value do
|
|
|
|
if expr do
|
|
|
|
{:cont, {:ok, Ash.Query.expr(^expr and ref(^key) == ^value)}}
|
|
|
|
else
|
|
|
|
{:cont, {:ok, Ash.Query.expr(ref(^key) == ^value)}}
|
|
|
|
end
|
|
|
|
else
|
|
|
|
{:halt, {:error, "Required key not present"}}
|
|
|
|
end
|
|
|
|
end)
|
2021-04-20 07:26:20 +12:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def identity_filter(identity, resource, arguments) do
|
|
|
|
{:ok,
|
|
|
|
resource
|
|
|
|
|> Ash.Resource.Info.identities()
|
|
|
|
|> Enum.find(&(&1.name == identity))
|
|
|
|
|> Map.get(:keys)
|
|
|
|
|> Enum.map(fn key ->
|
|
|
|
{key, Map.get(arguments, key)}
|
|
|
|
end)}
|
|
|
|
end
|
|
|
|
|
2022-10-12 17:51:01 +13:00
|
|
|
defp not_found(filter, resource, context, api) do
|
2021-03-16 08:51:17 +13:00
|
|
|
{:ok,
|
|
|
|
%{
|
|
|
|
result: nil,
|
2021-04-17 05:49:51 +12:00
|
|
|
errors:
|
2021-03-16 08:51:17 +13:00
|
|
|
to_errors(
|
|
|
|
Ash.Error.Query.NotFound.exception(
|
2021-06-29 05:33:01 +12:00
|
|
|
primary_key: Map.new(filter || []),
|
2021-03-16 08:51:17 +13:00
|
|
|
resource: resource
|
2022-10-12 17:51:01 +13:00
|
|
|
),
|
|
|
|
context,
|
|
|
|
api
|
2021-03-16 08:51:17 +13:00
|
|
|
)
|
|
|
|
}}
|
|
|
|
end
|
|
|
|
|
2021-06-05 07:07:12 +12:00
|
|
|
defp massage_filter(_resource, nil), do: nil
|
|
|
|
|
2021-06-09 09:37:59 +12:00
|
|
|
defp massage_filter(resource, filter) when is_map(filter) do
|
|
|
|
Map.new(filter, fn {key, value} ->
|
2021-06-05 07:07:12 +12:00
|
|
|
cond do
|
|
|
|
rel = Ash.Resource.Info.relationship(resource, key) ->
|
|
|
|
{key, massage_filter(rel.destination, value)}
|
|
|
|
|
|
|
|
Ash.Resource.Info.calculation(resource, key) ->
|
|
|
|
calc_input(key, value)
|
2021-06-09 09:37:59 +12:00
|
|
|
|
|
|
|
true ->
|
|
|
|
{key, value}
|
2021-06-05 07:07:12 +12:00
|
|
|
end
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
2021-06-09 09:37:59 +12:00
|
|
|
defp massage_filter(_resource, other), do: other
|
|
|
|
|
2021-06-05 07:07:12 +12:00
|
|
|
defp calc_input(key, value) do
|
|
|
|
case Map.fetch(value, :input) do
|
|
|
|
{:ok, input} ->
|
2021-07-02 14:59:16 +12:00
|
|
|
{key, {input, Map.delete(value, :input)}}
|
2021-06-05 07:07:12 +12:00
|
|
|
|
|
|
|
:error ->
|
|
|
|
{key, value}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-04-17 04:55:34 +12:00
|
|
|
defp clear_fields(nil, _, _), do: nil
|
|
|
|
|
2021-03-23 06:03:18 +13:00
|
|
|
defp clear_fields(result, resource, resolution) do
|
|
|
|
resolution
|
2022-09-21 15:07:30 +12:00
|
|
|
|> fields(["result"])
|
|
|
|
|> names_only()
|
2021-04-19 15:19:46 +12:00
|
|
|
|> Enum.map(fn identifier ->
|
2022-09-26 17:12:10 +13:00
|
|
|
Ash.Resource.Info.aggregate(resource, identifier)
|
2021-03-23 06:03:18 +13:00
|
|
|
end)
|
|
|
|
|> Enum.filter(& &1)
|
|
|
|
|> Enum.map(& &1.name)
|
|
|
|
|> Enum.reduce(result, fn field, result ->
|
|
|
|
Map.put(result, field, nil)
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
2022-09-21 15:07:30 +12:00
|
|
|
defp load_fields(query_or_record, resource, api, resolution, nested \\ []) do
|
|
|
|
fields = fields(resolution, nested)
|
2021-06-05 07:07:12 +12:00
|
|
|
|
|
|
|
fields
|
|
|
|
|> Enum.map(fn selection ->
|
|
|
|
aggregate = Ash.Resource.Info.aggregate(resource, selection.schema_node.identifier)
|
|
|
|
|
|
|
|
if aggregate do
|
|
|
|
aggregate.name
|
|
|
|
end
|
2021-05-15 05:01:41 +12:00
|
|
|
end)
|
|
|
|
|> Enum.filter(& &1)
|
|
|
|
|> case do
|
|
|
|
[] ->
|
|
|
|
{:ok, query_or_record}
|
2021-03-23 06:03:18 +13:00
|
|
|
|
2021-05-15 05:01:41 +12:00
|
|
|
loading ->
|
|
|
|
case query_or_record do
|
|
|
|
%Ash.Query{} = query ->
|
|
|
|
{:ok, Ash.Query.load(query, loading)}
|
2021-03-23 06:03:18 +13:00
|
|
|
|
2021-05-15 05:01:41 +12:00
|
|
|
record ->
|
|
|
|
api.load(record, loading)
|
|
|
|
end
|
2021-03-23 06:03:18 +13:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-09-21 15:07:30 +12:00
|
|
|
defp select_fields(query_or_changeset, resource, resolution, nested \\ []) do
|
2021-03-23 06:03:18 +13:00
|
|
|
subfields =
|
|
|
|
resolution
|
2021-04-10 08:29:50 +12:00
|
|
|
|> fields(nested)
|
2022-09-21 15:07:30 +12:00
|
|
|
|> names_only()
|
2021-04-16 08:28:04 +12:00
|
|
|
|> Enum.map(&field_or_relationship(resource, &1))
|
2021-03-23 06:03:18 +13:00
|
|
|
|> Enum.filter(& &1)
|
2022-09-21 15:07:30 +12:00
|
|
|
|> names_only()
|
2021-03-23 06:03:18 +13:00
|
|
|
|
|
|
|
case query_or_changeset do
|
|
|
|
%Ash.Query{} = query ->
|
2022-09-21 15:07:30 +12:00
|
|
|
query |> Ash.Query.select(subfields)
|
2021-03-23 06:03:18 +13:00
|
|
|
|
|
|
|
%Ash.Changeset{} = changeset ->
|
2022-09-21 15:07:30 +12:00
|
|
|
changeset |> Ash.Changeset.select(subfields)
|
2021-03-23 06:03:18 +13:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-04-19 15:19:46 +12:00
|
|
|
defp field_or_relationship(resource, identifier) do
|
|
|
|
case Ash.Resource.Info.attribute(resource, identifier) do
|
2021-04-16 08:28:04 +12:00
|
|
|
nil ->
|
2021-04-19 15:19:46 +12:00
|
|
|
case Ash.Resource.Info.relationship(resource, identifier) do
|
2021-04-16 08:28:04 +12:00
|
|
|
nil ->
|
|
|
|
nil
|
|
|
|
|
|
|
|
rel ->
|
2022-08-31 13:08:16 +12:00
|
|
|
Ash.Resource.Info.attribute(resource, rel.source_attribute)
|
2021-04-16 08:28:04 +12:00
|
|
|
end
|
|
|
|
|
|
|
|
attr ->
|
|
|
|
attr
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-09-21 15:07:30 +12:00
|
|
|
defp fields(resolution, []) do
|
|
|
|
resolution
|
|
|
|
|> Absinthe.Resolution.project()
|
|
|
|
end
|
|
|
|
|
|
|
|
defp fields(resolution, names) do
|
|
|
|
project =
|
2021-04-19 15:19:46 +12:00
|
|
|
resolution
|
|
|
|
|> Absinthe.Resolution.project()
|
2021-03-23 06:03:18 +13:00
|
|
|
|
2022-09-21 15:07:30 +12:00
|
|
|
Enum.reduce(names, {project, resolution.fields_cache}, fn name, {fields, cache} ->
|
|
|
|
case fields |> Enum.find(&(&1.name == name)) do
|
|
|
|
nil ->
|
|
|
|
{fields, cache}
|
|
|
|
|
|
|
|
path ->
|
|
|
|
type = Absinthe.Schema.lookup_type(resolution.schema, path.schema_node.type)
|
|
|
|
|
|
|
|
path
|
|
|
|
|> Map.get(:selections)
|
|
|
|
|> Absinthe.Resolution.Projector.project(
|
|
|
|
type,
|
|
|
|
resolution.path ++ [path],
|
|
|
|
cache,
|
|
|
|
resolution
|
|
|
|
)
|
|
|
|
end
|
2021-06-05 07:07:12 +12:00
|
|
|
end)
|
2022-09-21 15:07:30 +12:00
|
|
|
|> elem(0)
|
2021-06-05 07:07:12 +12:00
|
|
|
end
|
|
|
|
|
2022-09-21 15:07:30 +12:00
|
|
|
defp names_only(fields) do
|
|
|
|
Enum.map(fields, fn
|
|
|
|
%{schema_node: %{identifier: identifier}} ->
|
|
|
|
identifier
|
|
|
|
|
|
|
|
%{name: name} ->
|
|
|
|
name
|
|
|
|
end)
|
2021-06-05 07:07:12 +12:00
|
|
|
end
|
|
|
|
|
2021-01-22 17:06:06 +13:00
|
|
|
defp set_query_arguments(query, action, arg_values) do
|
2022-05-29 13:30:34 +12:00
|
|
|
action = Ash.Resource.Info.action(query.resource, action)
|
2021-01-22 17:06:06 +13:00
|
|
|
|
|
|
|
action.arguments
|
|
|
|
|> Enum.reject(& &1.private?)
|
|
|
|
|> Enum.reduce(query, fn argument, query ->
|
2022-09-23 05:31:40 +12:00
|
|
|
case Map.fetch(arg_values, argument.name) do
|
|
|
|
{:ok, value} ->
|
|
|
|
Ash.Query.set_argument(query, argument.name, value)
|
|
|
|
|
|
|
|
_ ->
|
|
|
|
query
|
|
|
|
end
|
2021-01-22 17:06:06 +13:00
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
2021-01-13 09:14:35 +13:00
|
|
|
defp destroy_opts(api, context, action) do
|
2022-08-31 13:08:16 +12:00
|
|
|
if AshGraphql.Api.Info.authorize?(api) do
|
2021-05-15 04:25:43 +12:00
|
|
|
[
|
|
|
|
actor: Map.get(context, :actor),
|
|
|
|
action: action,
|
2022-10-12 17:51:01 +13:00
|
|
|
verbose?: AshGraphql.Api.Info.debug?(api)
|
2021-05-15 04:25:43 +12:00
|
|
|
]
|
2021-01-13 09:14:35 +13:00
|
|
|
else
|
2021-05-15 04:25:43 +12:00
|
|
|
[
|
|
|
|
action: action,
|
2022-10-12 17:51:01 +13:00
|
|
|
verbose?: AshGraphql.Api.Info.debug?(api)
|
2021-05-15 04:25:43 +12:00
|
|
|
]
|
2021-01-13 09:14:35 +13:00
|
|
|
end
|
|
|
|
end
|
2020-08-14 09:39:59 +12:00
|
|
|
|
2021-09-10 03:49:36 +12:00
|
|
|
defp add_root_errors(resolution, api, {:error, error_or_errors}) do
|
2021-09-12 18:13:19 +12:00
|
|
|
do_root_errors(api, resolution, error_or_errors)
|
2021-09-09 16:27:49 +12:00
|
|
|
end
|
|
|
|
|
2021-09-10 03:49:36 +12:00
|
|
|
defp add_root_errors(resolution, api, [_, {:error, error_or_errors}]) do
|
2021-09-12 18:13:19 +12:00
|
|
|
do_root_errors(api, resolution, error_or_errors)
|
|
|
|
end
|
|
|
|
|
|
|
|
defp add_root_errors(resolution, api, [_, {:ok, %{errors: errors}}])
|
|
|
|
when errors not in [nil, []] do
|
|
|
|
do_root_errors(api, resolution, errors, false)
|
|
|
|
end
|
|
|
|
|
|
|
|
defp add_root_errors(resolution, api, {:ok, %{errors: errors}})
|
|
|
|
when errors not in [nil, []] do
|
|
|
|
do_root_errors(api, resolution, errors, false)
|
2021-09-09 16:27:49 +12:00
|
|
|
end
|
|
|
|
|
2021-09-10 03:49:36 +12:00
|
|
|
defp add_root_errors(resolution, _api, _other_thing) do
|
2021-09-09 16:27:49 +12:00
|
|
|
resolution
|
|
|
|
end
|
|
|
|
|
2021-09-12 18:13:19 +12:00
|
|
|
defp do_root_errors(api, resolution, error_or_errors, to_errors? \\ true) do
|
2022-08-31 13:08:16 +12:00
|
|
|
if AshGraphql.Api.Info.root_level_errors?(api) do
|
2021-09-12 18:13:19 +12:00
|
|
|
Map.update!(resolution, :errors, fn current_errors ->
|
|
|
|
if to_errors? do
|
2022-10-12 17:51:01 +13:00
|
|
|
Enum.concat(
|
|
|
|
current_errors || [],
|
|
|
|
List.wrap(to_errors(error_or_errors, resolution.context, api))
|
|
|
|
)
|
2021-09-12 18:13:19 +12:00
|
|
|
else
|
|
|
|
Enum.concat(current_errors || [], List.wrap(error_or_errors))
|
2021-03-23 06:03:18 +13:00
|
|
|
end
|
2021-09-12 18:13:19 +12:00
|
|
|
end)
|
|
|
|
else
|
|
|
|
resolution
|
2021-01-13 09:14:35 +13:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-02-11 05:33:31 +13:00
|
|
|
defp add_read_metadata({:error, error}, _, _) do
|
|
|
|
{:error, error}
|
|
|
|
end
|
|
|
|
|
2023-01-29 06:32:21 +13:00
|
|
|
defp add_read_metadata({:ok, result}, query, action) do
|
|
|
|
{:ok, add_read_metadata(result, query, action)}
|
|
|
|
end
|
|
|
|
|
|
|
|
defp add_read_metadata(nil, _, _), do: nil
|
|
|
|
|
|
|
|
defp add_read_metadata(result, query, action) when is_list(result) do
|
|
|
|
show_metadata = query.show_metadata || Enum.map(Map.get(action, :metadata, []), & &1.name)
|
|
|
|
|
|
|
|
Enum.map(result, fn record ->
|
|
|
|
do_add_read_metadata(record, show_metadata)
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
|
|
|
defp add_read_metadata(result, query, action) do
|
|
|
|
show_metadata = query.show_metadata || Enum.map(Map.get(action, :metadata, []), & &1.name)
|
|
|
|
|
|
|
|
do_add_read_metadata(result, show_metadata)
|
|
|
|
end
|
|
|
|
|
|
|
|
defp do_add_read_metadata(record, show_metadata) do
|
|
|
|
Enum.reduce(show_metadata, record, fn key, record ->
|
|
|
|
Map.put(record, key, Map.get(record.__metadata__ || %{}, key))
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
2021-08-30 07:23:28 +12:00
|
|
|
defp add_metadata(result, action_result, action) do
|
|
|
|
metadata = Map.get(action, :metadata, [])
|
|
|
|
|
|
|
|
if Enum.empty?(metadata) do
|
|
|
|
result
|
|
|
|
else
|
|
|
|
metadata =
|
|
|
|
Map.new(action.metadata, fn metadata ->
|
|
|
|
{metadata.name, Map.get(action_result.__metadata__ || %{}, metadata.name)}
|
|
|
|
end)
|
|
|
|
|
|
|
|
Map.put(result, :metadata, metadata)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-10-12 17:51:01 +13:00
|
|
|
defp destroy_result(result, initial, resource, changeset, api, resolution) do
|
2021-01-13 09:14:35 +13:00
|
|
|
case result do
|
2021-03-16 08:51:17 +13:00
|
|
|
:ok ->
|
2021-06-15 04:08:26 +12:00
|
|
|
{{:ok, %{result: clear_fields(initial, resource, resolution), errors: []}},
|
|
|
|
[changeset, :ok]}
|
2021-03-16 08:51:17 +13:00
|
|
|
|
2021-06-15 04:08:26 +12:00
|
|
|
{:error, %{changeset: changeset} = error} ->
|
2022-10-12 17:51:01 +13:00
|
|
|
{{:ok, %{result: nil, errors: to_errors(changeset.errors, resolution.context, api)}},
|
|
|
|
{:error, error}}
|
2021-01-13 09:14:35 +13:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-02-11 14:02:17 +13:00
|
|
|
@doc false
|
|
|
|
def unwrap_errors([]), do: []
|
2021-06-29 05:33:01 +12:00
|
|
|
|
2023-02-11 14:02:17 +13:00
|
|
|
def unwrap_errors(errors) do
|
2020-08-14 09:39:59 +12:00
|
|
|
errors
|
|
|
|
|> List.wrap()
|
2021-04-27 05:14:25 +12:00
|
|
|
|> Enum.flat_map(fn
|
2023-01-28 11:11:17 +13:00
|
|
|
%class{errors: errors} when class in [Ash.Error.Invalid, Ash.Error.Forbidden] ->
|
2021-06-29 05:33:01 +12:00
|
|
|
unwrap_errors(List.wrap(errors))
|
2021-04-27 05:14:25 +12:00
|
|
|
|
|
|
|
errors ->
|
|
|
|
List.wrap(errors)
|
|
|
|
end)
|
2021-06-29 05:33:01 +12:00
|
|
|
end
|
|
|
|
|
2022-10-12 17:51:01 +13:00
|
|
|
defp to_errors(errors, context, api) do
|
2023-02-11 14:02:17 +13:00
|
|
|
AshGraphql.Errors.to_errors(errors, context, api)
|
2020-08-14 09:39:59 +12:00
|
|
|
end
|
|
|
|
|
2023-02-23 07:18:19 +13:00
|
|
|
def resolve_calculation(%Absinthe.Resolution{state: :resolved} = resolution, _),
|
|
|
|
do: resolution
|
|
|
|
|
2022-09-26 17:12:10 +13:00
|
|
|
def resolve_calculation(
|
|
|
|
%{source: parent, arguments: args, context: %{loader: loader} = context} = resolution,
|
2022-11-23 07:27:53 +13:00
|
|
|
{api, resource, calculation}
|
2022-09-26 17:12:10 +13:00
|
|
|
) do
|
|
|
|
api_opts = [
|
|
|
|
actor: Map.get(context, :actor),
|
|
|
|
authorize?: AshGraphql.Api.Info.authorize?(api),
|
2022-10-12 17:51:01 +13:00
|
|
|
verbose?: AshGraphql.Api.Info.debug?(api)
|
2022-09-26 17:12:10 +13:00
|
|
|
]
|
|
|
|
|
2022-11-23 07:27:53 +13:00
|
|
|
tracer = AshGraphql.Api.Info.tracer(api)
|
|
|
|
|
2022-09-26 17:12:10 +13:00
|
|
|
opts = [
|
|
|
|
api_opts: api_opts,
|
|
|
|
type: :calculation,
|
2022-11-23 07:27:53 +13:00
|
|
|
resource: resource,
|
2022-09-26 17:12:10 +13:00
|
|
|
args: args,
|
2022-11-23 07:27:53 +13:00
|
|
|
tenant: Map.get(context, :tenant),
|
|
|
|
span_context: tracer && tracer.get_span_context()
|
2022-09-26 17:12:10 +13:00
|
|
|
]
|
|
|
|
|
|
|
|
batch_key = {calculation.name, opts}
|
|
|
|
|
|
|
|
do_dataloader(resolution, loader, api, batch_key, opts, parent)
|
|
|
|
end
|
|
|
|
|
2023-02-23 07:18:19 +13:00
|
|
|
def resolve_assoc(%Absinthe.Resolution{state: :resolved} = resolution, _),
|
|
|
|
do: resolution
|
|
|
|
|
2020-09-24 12:54:57 +12:00
|
|
|
def resolve_assoc(
|
2020-10-28 19:16:16 +13:00
|
|
|
%{source: parent, arguments: args, context: %{loader: loader} = context} = resolution,
|
2020-09-24 12:54:57 +12:00
|
|
|
{api, relationship}
|
|
|
|
) do
|
2021-02-24 06:28:19 +13:00
|
|
|
api_opts = [
|
|
|
|
actor: Map.get(context, :actor),
|
2022-08-31 13:08:16 +12:00
|
|
|
authorize?: AshGraphql.Api.Info.authorize?(api),
|
2022-10-12 17:51:01 +13:00
|
|
|
verbose?: AshGraphql.Api.Info.debug?(api)
|
2021-02-24 06:28:19 +13:00
|
|
|
]
|
2020-09-29 02:42:36 +13:00
|
|
|
|
2021-05-05 05:01:15 +12:00
|
|
|
args
|
2023-03-18 07:55:55 +13:00
|
|
|
|> apply_load_arguments(Ash.Query.new(relationship.destination))
|
2021-05-05 05:01:15 +12:00
|
|
|
|> select_fields(relationship.destination, resolution)
|
|
|
|
|> load_fields(relationship.destination, api, resolution)
|
|
|
|
|> case do
|
|
|
|
{:ok, related_query} ->
|
2022-11-23 07:27:53 +13:00
|
|
|
tracer = AshGraphql.Api.Info.tracer(api)
|
|
|
|
|
2021-05-05 05:01:15 +12:00
|
|
|
opts = [
|
|
|
|
query: related_query,
|
|
|
|
api_opts: api_opts,
|
2022-09-26 17:12:10 +13:00
|
|
|
type: :relationship,
|
2021-05-05 05:01:15 +12:00
|
|
|
args: args,
|
2022-09-26 17:12:10 +13:00
|
|
|
resource: relationship.source,
|
2022-11-23 07:27:53 +13:00
|
|
|
tenant: Map.get(context, :tenant),
|
|
|
|
span_context: tracer && tracer.get_span_context()
|
2021-05-05 05:01:15 +12:00
|
|
|
]
|
2020-09-29 02:42:36 +13:00
|
|
|
|
2022-09-26 17:12:10 +13:00
|
|
|
batch_key = {relationship.name, opts}
|
2021-05-05 05:01:15 +12:00
|
|
|
do_dataloader(resolution, loader, api, batch_key, args, parent)
|
2020-08-14 09:39:59 +12:00
|
|
|
|
2021-05-05 05:01:15 +12:00
|
|
|
{:error, error} ->
|
2022-10-12 17:51:01 +13:00
|
|
|
Absinthe.Resolution.put_result(resolution, to_resolution({:error, error}, context, api))
|
2021-05-05 05:01:15 +12:00
|
|
|
end
|
2020-08-14 09:39:59 +12:00
|
|
|
end
|
|
|
|
|
2023-02-23 07:18:19 +13:00
|
|
|
def resolve_id(%Absinthe.Resolution{state: :resolved} = resolution, _),
|
|
|
|
do: resolution
|
|
|
|
|
2021-08-04 10:56:08 +12:00
|
|
|
def resolve_id(
|
|
|
|
%{source: parent} = resolution,
|
|
|
|
{_resource, field}
|
|
|
|
) do
|
|
|
|
Absinthe.Resolution.put_result(resolution, {:ok, Map.get(parent, field)})
|
|
|
|
end
|
|
|
|
|
2023-02-23 07:18:19 +13:00
|
|
|
def resolve_keyset(%Absinthe.Resolution{state: :resolved} = resolution, _),
|
|
|
|
do: resolution
|
|
|
|
|
2022-10-21 10:51:10 +13:00
|
|
|
def resolve_keyset(
|
|
|
|
%{source: parent} = resolution,
|
|
|
|
_field
|
|
|
|
) do
|
|
|
|
parent.__metadata__
|
|
|
|
Absinthe.Resolution.put_result(resolution, {:ok, Map.get(parent.__metadata__, :keyset)})
|
|
|
|
end
|
|
|
|
|
2023-02-23 07:18:19 +13:00
|
|
|
def resolve_composite_id(%Absinthe.Resolution{state: :resolved} = resolution, _),
|
|
|
|
do: resolution
|
|
|
|
|
2021-08-04 10:56:08 +12:00
|
|
|
def resolve_composite_id(
|
|
|
|
%{source: parent} = resolution,
|
|
|
|
{_resource, _fields}
|
|
|
|
) do
|
|
|
|
Absinthe.Resolution.put_result(
|
|
|
|
resolution,
|
|
|
|
{:ok, AshGraphql.Resource.encode_primary_key(parent)}
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
def query_complexity(
|
|
|
|
%{limit: limit},
|
|
|
|
child_complexity,
|
|
|
|
_
|
|
|
|
) do
|
|
|
|
if child_complexity == 0 do
|
|
|
|
1
|
|
|
|
else
|
|
|
|
limit * child_complexity
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def query_complexity(
|
|
|
|
_,
|
|
|
|
child_complexity,
|
|
|
|
_
|
|
|
|
) do
|
|
|
|
child_complexity + 1
|
|
|
|
end
|
|
|
|
|
2022-10-12 17:51:01 +13:00
|
|
|
def fetch_dataloader(loader, api, batch_key, context, parent) do
|
|
|
|
to_resolution(Dataloader.get(loader, api, batch_key, parent), context, api)
|
2022-05-12 04:45:49 +12:00
|
|
|
end
|
|
|
|
|
2020-09-24 12:54:57 +12:00
|
|
|
defp do_dataloader(
|
|
|
|
resolution,
|
|
|
|
loader,
|
|
|
|
api,
|
|
|
|
batch_key,
|
2020-10-28 19:16:16 +13:00
|
|
|
_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 ->
|
2022-10-12 17:51:01 +13:00
|
|
|
fetch_dataloader(loader, api, batch_key, resolution.context, parent)
|
2020-09-24 12:54:57 +12:00
|
|
|
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 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 ->
|
2023-03-18 07:55:55 +13:00
|
|
|
Ash.Query.do_filter(query, massage_filter(query.resource, value))
|
2020-11-12 16:41:54 +13:00
|
|
|
|
|
|
|
{:sort, value}, query ->
|
|
|
|
keyword_sort =
|
2023-03-18 07:55:55 +13:00
|
|
|
Enum.map(value, fn %{order: order, field: field} = input ->
|
|
|
|
case Ash.Resource.Info.calculation(query.resource, field) do
|
|
|
|
%{arguments: [_ | _]} ->
|
|
|
|
input_name = String.to_existing_atom("#{field}_input")
|
|
|
|
|
|
|
|
{field, {order, input[input_name] || %{}}}
|
|
|
|
|
|
|
|
_ ->
|
|
|
|
{field, order}
|
|
|
|
end
|
2020-11-12 16:41:54 +13:00
|
|
|
end)
|
|
|
|
|
|
|
|
Ash.Query.sort(query, keyword_sort)
|
2020-08-15 02:20:47 +12:00
|
|
|
|
2023-03-18 07:55:55 +13:00
|
|
|
_, query ->
|
|
|
|
query
|
|
|
|
end)
|
2020-08-15 02:20:47 +12:00
|
|
|
end
|
|
|
|
|
2022-10-12 17:51:01 +13:00
|
|
|
defp to_resolution({:ok, value}, _context, _api), do: {:ok, value}
|
2020-08-14 09:39:59 +12:00
|
|
|
|
2022-10-12 17:51:01 +13:00
|
|
|
defp to_resolution({:error, error}, context, api) do
|
2021-05-05 05:01:15 +12:00
|
|
|
{:error,
|
|
|
|
error
|
2021-06-29 05:33:01 +12:00
|
|
|
|> unwrap_errors()
|
2021-05-05 05:01:15 +12:00
|
|
|
|> Enum.map(fn error ->
|
|
|
|
if AshGraphql.Error.impl_for(error) do
|
2022-10-12 17:51:01 +13:00
|
|
|
error = AshGraphql.Error.to_error(error)
|
|
|
|
|
|
|
|
case AshGraphql.Api.Info.error_handler(api) do
|
|
|
|
nil ->
|
|
|
|
error
|
|
|
|
|
|
|
|
{m, f, a} ->
|
|
|
|
apply(m, f, [error, context | a])
|
|
|
|
end
|
2021-05-05 05:01:15 +12:00
|
|
|
else
|
|
|
|
uuid = Ash.UUID.generate()
|
|
|
|
|
2021-06-05 07:07:12 +12:00
|
|
|
stacktrace =
|
|
|
|
case error do
|
|
|
|
%{stacktrace: %{stacktrace: v}} ->
|
|
|
|
v
|
|
|
|
|
|
|
|
_ ->
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
|
2021-05-05 05:01:15 +12:00
|
|
|
Logger.warn(
|
2021-08-04 10:56:08 +12:00
|
|
|
"`#{uuid}`: AshGraphql.Error not implemented for error:\n\n#{Exception.format(:error, error, stacktrace)}"
|
2021-05-05 05:01:15 +12:00
|
|
|
)
|
|
|
|
|
2022-12-14 11:47:58 +13:00
|
|
|
if AshGraphql.Api.Info.show_raised_errors?(api) do
|
|
|
|
%{
|
|
|
|
message: """
|
|
|
|
Raised error: #{uuid}
|
|
|
|
|
|
|
|
#{Exception.format(:error, error, stacktrace)}"
|
|
|
|
"""
|
|
|
|
}
|
|
|
|
else
|
|
|
|
%{
|
|
|
|
message: "Something went wrong. Unique error id: `#{uuid}`"
|
|
|
|
}
|
|
|
|
end
|
2021-05-05 05:01:15 +12:00
|
|
|
end
|
|
|
|
end)}
|
|
|
|
end
|
2020-05-02 10:35:12 +12:00
|
|
|
end
|