mirror of
https://github.com/ash-project/ash_json_api_wrapper.git
synced 2024-09-20 05:12:51 +12:00
a9d94bdd9b
improvement: new spark_function_behaviour for `before_request`
415 lines
12 KiB
Elixir
415 lines
12 KiB
Elixir
defmodule AshJsonApiWrapper.Filter do
|
|
@moduledoc false
|
|
|
|
def find_simple_filter(%Ash.Filter{expression: expression}, field) do
|
|
find_simple_filter(expression, field)
|
|
end
|
|
|
|
def find_simple_filter(
|
|
%Ash.Query.BooleanExpression{op: :and, left: left, right: right} = expr,
|
|
field
|
|
) do
|
|
case find_simple_filter(left, field) do
|
|
{:ok, nil} ->
|
|
case find_simple_filter(right, field) do
|
|
{:ok, nil} ->
|
|
{:ok, expr, []}
|
|
|
|
{:ok, {right_remaining, right_instructions}} ->
|
|
{:ok, Ash.Query.BooleanExpression.new(:and, left, right_remaining),
|
|
right_instructions}
|
|
end
|
|
|
|
{:ok, {left_remaining, left_instructions}} ->
|
|
case find_simple_filter(right, field) do
|
|
{:ok, nil} ->
|
|
{:ok,
|
|
{Ash.Query.BooleanExpression.new(:and, left_remaining, right), left_instructions}}
|
|
|
|
{:ok, {right_remaining, right_instructions}} ->
|
|
{:ok,
|
|
{Ash.Query.BooleanExpression.new(:and, left_remaining, right_remaining),
|
|
left_instructions ++ right_instructions}}
|
|
end
|
|
end
|
|
end
|
|
|
|
def find_simple_filter(
|
|
%Ash.Query.Operator.Eq{left: left, right: %Ash.Query.Ref{} = right} = op,
|
|
field
|
|
) do
|
|
find_simple_filter(%{op | right: left, left: right}, field)
|
|
end
|
|
|
|
def find_simple_filter(
|
|
%Ash.Query.Operator.Eq{
|
|
left: %Ash.Query.Ref{relationship_path: [], attribute: %{name: name}}
|
|
},
|
|
field
|
|
)
|
|
when name != field do
|
|
{:ok, nil}
|
|
end
|
|
|
|
def find_simple_filter(
|
|
%Ash.Query.Operator.Eq{
|
|
left: %Ash.Query.Ref{relationship_path: [], attribute: %{name: field}},
|
|
right: value
|
|
},
|
|
field
|
|
) do
|
|
{:ok, {nil, [{:set, field, value}]}}
|
|
end
|
|
|
|
def find_simple_filter(
|
|
%Ash.Query.Operator.In{
|
|
left: %Ash.Query.Ref{relationship_path: [], attribute: %{name: field}},
|
|
right: values
|
|
},
|
|
field
|
|
) do
|
|
{:ok, {nil, [{:expand_set, field, values}]}}
|
|
end
|
|
|
|
def find_simple_filter(_, _) do
|
|
{:ok, nil}
|
|
end
|
|
|
|
def find_place_in_list_filter(
|
|
filter,
|
|
field,
|
|
path,
|
|
context \\ %{in_an_or?: false, other_branch_instructions: nil}
|
|
)
|
|
|
|
def find_place_in_list_filter(nil, _, _, _), do: {:ok, nil}
|
|
|
|
def find_place_in_list_filter(%Ash.Filter{expression: expression}, field, path, context) do
|
|
find_place_in_list_filter(expression, field, path, context)
|
|
end
|
|
|
|
def find_place_in_list_filter(
|
|
%Ash.Query.BooleanExpression{op: op, left: left, right: right} = expr,
|
|
field,
|
|
path,
|
|
context
|
|
) do
|
|
case find_place_in_list_filter(left, field, path, context) do
|
|
{:ok, nil} ->
|
|
case find_place_in_list_filter(right, field, path, context) do
|
|
{:ok, nil} ->
|
|
{:ok, expr, []}
|
|
|
|
{:ok, {right_remaining, right_instructions}} ->
|
|
{:ok, Ash.Query.BooleanExpression.new(op, left, right_remaining), right_instructions}
|
|
end
|
|
|
|
{:ok, {left_remaining, left_instructions}} ->
|
|
case find_place_in_list_filter(right, field, path, %{
|
|
context
|
|
| other_branch_instructions: left_instructions
|
|
}) do
|
|
{:ok, nil} ->
|
|
{:ok, {Ash.Query.BooleanExpression.new(op, left_remaining, right), left_instructions}}
|
|
|
|
{:ok, {right_remaining, right_instructions}} ->
|
|
{:ok,
|
|
{Ash.Query.BooleanExpression.new(op, left_remaining, right_remaining),
|
|
left_instructions ++ right_instructions}}
|
|
end
|
|
end
|
|
end
|
|
|
|
def find_place_in_list_filter(
|
|
%Ash.Query.Operator.Eq{left: left, right: %Ash.Query.Ref{} = right} = op,
|
|
field,
|
|
path,
|
|
context
|
|
) do
|
|
find_place_in_list_filter(%{op | right: left, left: right}, field, path, context)
|
|
end
|
|
|
|
def find_place_in_list_filter(
|
|
%Ash.Query.Operator.In{left: left, right: %Ash.Query.Ref{} = right} = op,
|
|
field,
|
|
path,
|
|
context
|
|
) do
|
|
find_place_in_list_filter(%{op | right: left, left: right}, field, path, context)
|
|
end
|
|
|
|
def find_place_in_list_filter(
|
|
%Ash.Query.Operator.Eq{
|
|
left: %Ash.Query.Ref{relationship_path: [], attribute: %{name: name}}
|
|
},
|
|
field,
|
|
_path,
|
|
_context
|
|
)
|
|
when name != field do
|
|
{:ok, nil}
|
|
end
|
|
|
|
def find_place_in_list_filter(
|
|
%Ash.Query.Operator.In{
|
|
left: %Ash.Query.Ref{relationship_path: [], attribute: %{name: name}}
|
|
},
|
|
field,
|
|
_path,
|
|
_context
|
|
)
|
|
when name != field do
|
|
{:ok, nil}
|
|
end
|
|
|
|
def find_place_in_list_filter(
|
|
%Ash.Query.Operator.Eq{
|
|
left: %Ash.Query.Ref{relationship_path: [], attribute: %{name: field}},
|
|
right: value
|
|
},
|
|
field,
|
|
path,
|
|
_context
|
|
) do
|
|
{:ok, {nil, [{:place_in_list, path, value}]}}
|
|
end
|
|
|
|
def find_place_in_list_filter(
|
|
%Ash.Query.Operator.In{
|
|
left: %Ash.Query.Ref{relationship_path: [], attribute: %{name: field}},
|
|
right: values
|
|
},
|
|
field,
|
|
path,
|
|
_context
|
|
) do
|
|
{:ok, {nil, Enum.map(values, &{:place_in_list, path, &1})}}
|
|
end
|
|
|
|
def find_filter_that_uses_get_endpoint(
|
|
expr,
|
|
resource,
|
|
action,
|
|
templates \\ nil,
|
|
in_an_or? \\ false,
|
|
uses_endpoint \\ nil
|
|
)
|
|
|
|
def find_filter_that_uses_get_endpoint(
|
|
%Ash.Filter{expression: expression},
|
|
resource,
|
|
action,
|
|
templates,
|
|
in_an_or?,
|
|
uses_endpoint
|
|
) do
|
|
find_filter_that_uses_get_endpoint(
|
|
expression,
|
|
resource,
|
|
action,
|
|
templates,
|
|
in_an_or?,
|
|
uses_endpoint
|
|
)
|
|
end
|
|
|
|
def find_filter_that_uses_get_endpoint(
|
|
%Ash.Query.BooleanExpression{op: :and, left: left, right: right},
|
|
resource,
|
|
action,
|
|
templates,
|
|
in_an_or?,
|
|
uses_endpoint
|
|
) do
|
|
case find_filter_that_uses_get_endpoint(left, resource, action, templates, in_an_or?) do
|
|
{:ok, {left_remaining, get_endpoint, left_templates}} ->
|
|
if uses_endpoint && get_endpoint != uses_endpoint do
|
|
{:error,
|
|
"Filter would cause the usage of different endpoints: #{inspect(uses_endpoint)} and #{inspect(get_endpoint)}"}
|
|
else
|
|
case find_filter_that_uses_get_endpoint(right, resource, action, templates, in_an_or?) do
|
|
{:ok, {right_remaining, get_endpoint, right_templates}} ->
|
|
if uses_endpoint && get_endpoint != uses_endpoint do
|
|
{:error,
|
|
"Filter would cause the usage of different endpoints: #{inspect(uses_endpoint)} and #{inspect(get_endpoint)}"}
|
|
else
|
|
{:ok,
|
|
{Ash.Query.BooleanExpression.new(:and, left_remaining, right_remaining),
|
|
uses_endpoint, add_templates([left_templates, right_templates, templates])}}
|
|
end
|
|
|
|
{:ok, nil} ->
|
|
{:ok,
|
|
{Ash.Query.BooleanExpression.new(:and, left_remaining, right), uses_endpoint,
|
|
add_templates([left_templates, templates])}}
|
|
|
|
{:error, error} ->
|
|
{:error, error}
|
|
end
|
|
end
|
|
|
|
{:ok, nil} ->
|
|
case find_filter_that_uses_get_endpoint(right, resource, action, templates, in_an_or?) do
|
|
{:ok, {right_remaining, get_endpoint, right_templates}} ->
|
|
if uses_endpoint && get_endpoint != uses_endpoint do
|
|
{:error,
|
|
"Filter would cause the usage of different endpoints: #{inspect(uses_endpoint)} and #{inspect(get_endpoint)}"}
|
|
else
|
|
{:ok,
|
|
{Ash.Query.BooleanExpression.new(:and, left, right_remaining), uses_endpoint,
|
|
add_templates([right_templates, templates])}}
|
|
end
|
|
end
|
|
|
|
{:error, error} ->
|
|
{:error, error}
|
|
end
|
|
end
|
|
|
|
def find_filter_that_uses_get_endpoint(
|
|
%Ash.Query.BooleanExpression{op: :or, left: left, right: right} = expr,
|
|
resource,
|
|
action,
|
|
templates,
|
|
_in_an_or?,
|
|
uses_endpoint
|
|
) do
|
|
case find_filter_that_uses_get_endpoint(left, resource, action, templates, true) do
|
|
{:ok, nil} ->
|
|
case find_filter_that_uses_get_endpoint(right, resource, action, templates, true) do
|
|
{:ok, nil} ->
|
|
{:ok, {expr, uses_endpoint, nil}}
|
|
|
|
{:error, error} ->
|
|
{:error, error}
|
|
end
|
|
|
|
{:error, error} ->
|
|
{:error, error}
|
|
|
|
_ ->
|
|
raise "Unreachable!"
|
|
end
|
|
end
|
|
|
|
def find_filter_that_uses_get_endpoint(
|
|
%Ash.Query.Operator.Eq{left: %Ash.Query.Ref{}, right: %Ash.Query.Ref{}} = expr,
|
|
_,
|
|
_,
|
|
_,
|
|
_,
|
|
uses_endpoint
|
|
) do
|
|
{:ok, {expr, uses_endpoint, nil}}
|
|
end
|
|
|
|
def find_filter_that_uses_get_endpoint(
|
|
%Ash.Query.Operator.Eq{
|
|
left: left,
|
|
right: %Ash.Query.Ref{} = right
|
|
} = op,
|
|
resource,
|
|
action,
|
|
templates,
|
|
in_an_or?,
|
|
uses_endpoint
|
|
) do
|
|
find_filter_that_uses_get_endpoint(
|
|
%{op | right: left, left: right},
|
|
resource,
|
|
action,
|
|
templates,
|
|
in_an_or?,
|
|
uses_endpoint
|
|
)
|
|
end
|
|
|
|
def find_filter_that_uses_get_endpoint(
|
|
%Ash.Query.Operator.In{
|
|
left: left,
|
|
right: %Ash.Query.Ref{} = right
|
|
} = op,
|
|
resource,
|
|
action,
|
|
templates,
|
|
in_an_or?,
|
|
uses_endpoint
|
|
) do
|
|
find_filter_that_uses_get_endpoint(
|
|
%{op | right: left, left: right},
|
|
resource,
|
|
action,
|
|
templates,
|
|
in_an_or?,
|
|
uses_endpoint
|
|
)
|
|
end
|
|
|
|
def find_filter_that_uses_get_endpoint(
|
|
%Ash.Query.Operator.Eq{
|
|
left: %Ash.Query.Ref{relationship_path: [], attribute: attribute},
|
|
right: value
|
|
},
|
|
resource,
|
|
action,
|
|
templates,
|
|
in_an_or?,
|
|
uses_endpoint
|
|
) do
|
|
case AshJsonApiWrapper.DataLayer.Info.get_endpoint(resource, action.name, attribute.name) do
|
|
nil ->
|
|
{:ok, nil}
|
|
|
|
get_endpoint ->
|
|
if in_an_or? do
|
|
{:error, "Cannot use get_endpoint attributes in an `or` clause of a filter."}
|
|
else
|
|
if uses_endpoint && get_endpoint != uses_endpoint do
|
|
{:error,
|
|
"Filter would cause the usage of different endpoints: #{inspect(uses_endpoint)} and #{inspect(get_endpoint)}"}
|
|
else
|
|
{:ok, {nil, get_endpoint, add_templates([[{attribute.name, value}], templates])}}
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def find_filter_that_uses_get_endpoint(
|
|
%Ash.Query.Operator.In{
|
|
left: %Ash.Query.Ref{relationship_path: [], attribute: attribute},
|
|
right: values
|
|
},
|
|
resource,
|
|
action,
|
|
templates,
|
|
in_an_or?,
|
|
uses_endpoint
|
|
) do
|
|
case AshJsonApiWrapper.DataLayer.Info.get_endpoint(resource, action.name, attribute.name) do
|
|
nil ->
|
|
{:ok, nil}
|
|
|
|
get_endpoint ->
|
|
if in_an_or? do
|
|
{:error, "Cannot use get_endpoint attributes in an `or` clause of a filter."}
|
|
else
|
|
if uses_endpoint && get_endpoint != uses_endpoint do
|
|
{:error,
|
|
"Filter would cause the usage of different endpoints: #{inspect(uses_endpoint)} and #{inspect(get_endpoint)}"}
|
|
else
|
|
{:ok,
|
|
{nil, get_endpoint,
|
|
add_templates([Enum.map(values, &{attribute.name, &1}), templates])}}
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
defp add_templates(templates) do
|
|
if Enum.all?(templates, &is_nil/1) do
|
|
nil
|
|
else
|
|
Enum.flat_map(templates, &List.wrap/1)
|
|
end
|
|
end
|
|
end
|