ash_json_api_wrapper/lib/filter.ex

427 lines
12 KiB
Elixir
Raw Normal View History

2021-11-05 18:13:33 +13:00
defmodule AshJsonApiWrapper.Filter do
@moduledoc false
def find_simple_filter(%Ash.Filter{expression: expression}, field) do
find_simple_filter(expression, field)
2021-11-05 18:13:33 +13:00
end
def find_simple_filter(
%Ash.Query.BooleanExpression{op: :and, left: left, right: right} = expr,
field
2021-11-05 18:13:33 +13:00
) do
case find_simple_filter(left, field) do
2021-11-05 18:13:33 +13:00
{:ok, nil} ->
case find_simple_filter(right, field) do
2021-11-05 18:13:33 +13:00
{:ok, nil} ->
{:ok, expr, []}
{:ok, {right_remaining, right_instructions}} ->
{:ok, Ash.Query.BooleanExpression.new(:and, left, right_remaining),
right_instructions}
2021-11-05 18:13:33 +13:00
end
{:ok, {left_remaining, left_instructions}} ->
case find_simple_filter(right, field) do
2021-11-05 18:13:33 +13:00
{:ok, nil} ->
{:ok,
{Ash.Query.BooleanExpression.new(:and, left_remaining, right), left_instructions}}
2021-11-05 18:13:33 +13:00
{:ok, {right_remaining, right_instructions}} ->
{:ok,
{Ash.Query.BooleanExpression.new(:and, left_remaining, right_remaining),
2021-11-05 18:13:33 +13:00
left_instructions ++ right_instructions}}
end
end
end
def find_simple_filter(
%Ash.Query.Operator.Eq{left: left, right: %Ash.Query.Ref{} = right} = op,
field
2021-11-05 18:13:33 +13:00
) do
find_simple_filter(%{op | right: left, left: right}, field)
2021-11-05 18:13:33 +13:00
end
def find_simple_filter(
%Ash.Query.Operator.Eq{
left: %Ash.Query.Ref{relationship_path: [], attribute: %{name: name}}
},
field
2021-11-05 18:13:33 +13:00
)
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
2021-11-05 18:13:33 +13:00
) 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}
2021-11-05 18:13:33 +13:00
end
def find_place_in_list_filter(
filter,
field,
path,
type,
2021-11-05 18:13:33 +13:00
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, type, context) do
find_place_in_list_filter(expression, field, path, type, context)
2021-11-05 18:13:33 +13:00
end
def find_place_in_list_filter(
%Ash.Query.BooleanExpression{op: op, left: left, right: right} = expr,
field,
path,
type,
2021-11-05 18:13:33 +13:00
context
) do
case find_place_in_list_filter(left, field, path, type, context) do
2021-11-05 18:13:33 +13:00
{:ok, nil} ->
case find_place_in_list_filter(right, field, path, type, context) do
2021-11-05 18:13:33 +13:00
{: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,
type,
2021-11-05 18:13:33 +13:00
context
) do
find_place_in_list_filter(%{op | right: left, left: right}, field, path, type, context)
2021-11-05 18:13:33 +13:00
end
def find_place_in_list_filter(
%Ash.Query.Operator.In{left: left, right: %Ash.Query.Ref{} = right} = op,
field,
path,
type,
2021-11-05 18:13:33 +13:00
context
) do
find_place_in_list_filter(%{op | right: left, left: right}, field, path, type, context)
2021-11-05 18:13:33 +13:00
end
def find_place_in_list_filter(
%Ash.Query.Operator.Eq{
left: %Ash.Query.Ref{relationship_path: [], attribute: %{name: name}}
},
field,
_path,
_type,
2021-11-05 18:13:33 +13:00
_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,
_type,
2021-11-05 18:13:33 +13:00
_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,
type,
2021-11-05 18:13:33 +13:00
_context
) do
{:ok, {nil, [{type, path, value}]}}
2021-11-05 18:13:33 +13:00
end
def find_place_in_list_filter(
%Ash.Query.Operator.In{
left: %Ash.Query.Ref{relationship_path: [], attribute: %{name: field}},
right: values
},
field,
path,
type,
2021-11-05 18:13:33 +13:00
_context
) do
{:ok, {nil, Enum.map(values, &{type, path, &1})}}
2021-11-05 18:13:33 +13:00
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
{:ok, nil} ->
{:ok, {Ash.Query.BooleanExpression.new(:and, left, right), uses_endpoint, nil}}
2021-11-05 18:13:33 +13:00
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,
2021-11-05 18:13:33 +13:00
_,
_,
_,
_,
uses_endpoint
2021-11-05 18:13:33 +13:00
) do
{:ok, {expr, uses_endpoint, nil}}
2021-11-05 18:13:33 +13:00
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
2022-09-16 05:06:29 +12:00
case AshJsonApiWrapper.DataLayer.Info.get_endpoint(resource, action.name, attribute.name) do
2021-11-05 18:13:33 +13:00
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
2022-09-16 05:06:29 +12:00
case AshJsonApiWrapper.DataLayer.Info.get_endpoint(resource, action.name, attribute.name) do
2021-11-05 18:13:33 +13:00
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