fix: properly error on types when evaluating expressions at runtime

This commit is contained in:
Zach Daniel 2022-09-14 22:31:32 -04:00
parent 4b88300e00
commit a5f51e8f1b
5 changed files with 57 additions and 10 deletions

View file

@ -195,12 +195,21 @@ defmodule Ash.Filter.Runtime do
nil ->
{:ok, true}
%op{__operator__?: true, left: left, right: right} = operator ->
%op{__operator__?: true, left: left, right: right} ->
with {:ok, [left, right]} <-
resolve_exprs([left, right], record),
{:known, val} <- op.evaluate(%{operator | left: left, right: right}) do
{:op, {:ok, %op{} = new_operator}} <-
{:op, Ash.Query.Operator.try_cast_with_ref(op, left, right)},
{:known, val} <-
op.evaluate(new_operator) do
{:ok, val}
else
{:op, {:error, error}} ->
{:error, error}
{:op, {:ok, expr}} ->
do_match(record, expr)
{:error, error} ->
{:error, error}
@ -213,9 +222,15 @@ defmodule Ash.Filter.Runtime do
%func{__function__?: true, arguments: arguments} = function ->
with {:ok, args} <- resolve_exprs(arguments, record),
{:args, args} when not is_nil(args) <-
{:args, try_cast_arguments(func.args(), args)},
{:known, val} <- func.evaluate(%{function | arguments: args}) do
{:ok, val}
else
{:args, nil} ->
{:error,
"Could not cast function arguments for #{func.name()}/#{Enum.count(arguments)}"}
{:error, error} ->
{:error, error}
@ -333,11 +348,19 @@ defmodule Ash.Filter.Runtime do
end)
end
defp resolve_expr(%mod{__predicate__?: _, left: left, right: right} = pred, record) do
defp resolve_expr(%mod{__predicate__?: _, left: left, right: right}, record) do
with {:ok, [left, right]} <- resolve_exprs([left, right], record),
{:known, val} <- mod.evaluate(%{pred | left: left, right: right}) do
{:op, {:ok, %mod{} = new_pred}} <-
{:op, Ash.Query.Operator.try_cast_with_ref(mod, left, right)},
{:known, val} <- mod.evaluate(new_pred) do
{:ok, val}
else
{:op, {:error, error}} ->
{:error, error}
{:op, {:ok, expr}} ->
resolve_expr(expr, record)
{:error, error} ->
{:error, error}
@ -351,9 +374,14 @@ defmodule Ash.Filter.Runtime do
defp resolve_expr(%mod{__predicate__?: _, arguments: args} = pred, record) do
with {:ok, args} <- resolve_exprs(args, record),
{:args, args} when not is_nil(args) <-
{:args, try_cast_arguments(mod.args(), args)},
{:known, val} <- mod.evaluate(%{pred | arguments: args}) do
{:ok, val}
else
{:args, nil} ->
{:error, "Could not cast function arguments for #{mod.name()}/#{Enum.count(args)}"}
{:error, error} ->
{:error, error}
@ -367,6 +395,16 @@ defmodule Ash.Filter.Runtime do
defp resolve_expr(other, _), do: {:ok, other}
defp try_cast_arguments(configured_args, args) do
given_arg_count = Enum.count(args)
configured_args
|> Enum.filter(fn args ->
Enum.count(args) == given_arg_count
end)
|> Enum.find_value(&Ash.Query.Function.try_cast_arguments(&1, args))
end
defp resolve_ref(%Ref{attribute: attribute, relationship_path: path}, record) do
name =
case attribute do

View file

@ -80,7 +80,7 @@ defmodule Ash.Query.Function do
end
end
defp try_cast_arguments(configured_args, args) do
def try_cast_arguments(configured_args, args) do
args
|> Enum.zip(configured_args)
|> Enum.reduce_while({:ok, []}, fn

View file

@ -71,7 +71,8 @@ defmodule Ash.Query.Operator do
end
end
defp try_cast_with_ref(mod, left, right) do
@doc false
def try_cast_with_ref(mod, left, right) do
Enum.find_value(mod.types(), fn type ->
try_cast(left, right, type)
end)

View file

@ -14,6 +14,8 @@ defmodule Ash.Query.Type do
:error -> :error
{:ok, val} -> {:ok, val}
end
else
:error
end
end

View file

@ -104,12 +104,18 @@ defmodule Ash.Test.CalculationTest do
calculate :conditional_full_name,
:string,
expr(if(first_name and last_name, first_name <> " " <> last_name, "(none)"))
expr(
if(
not is_nil(first_name) and not is_nil(last_name),
first_name <> " " <> last_name,
"(none)"
)
)
calculate :conditional_full_name_block,
:string,
expr(
if first_name and last_name do
if not is_nil(first_name) and not is_nil(last_name) do
first_name <> " " <> last_name
else
"(none)"
@ -120,10 +126,10 @@ defmodule Ash.Test.CalculationTest do
:string,
expr(
cond do
first_name and last_name ->
not is_nil(first_name) and not is_nil(last_name) ->
first_name <> " " <> last_name
first_name ->
not is_nil(first_name) ->
first_name
true ->