Skip to content

Commit

Permalink
fix: properly apply calculations with arguments in filter form
Browse files Browse the repository at this point in the history
fixes #241
  • Loading branch information
zachdaniel committed Sep 26, 2024
1 parent 9d09c7a commit 3a50f74
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 27 deletions.
93 changes: 67 additions & 26 deletions lib/ash_phoenix/filter_form/filter_form.ex
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,11 @@ defmodule AshPhoenix.FilterForm do
return a list of ash phoenix formatted errors, e.g `[{field :: atom, message :: String.t(), substituations :: Keyword.t()}]`
"""
],
warn_on_unhandled_errors?: [
type: :boolean,
default: true,
doc: "Whether or not to emit warning log on unhandled form errors"
],
remove_empty_groups?: [
type: :boolean,
doc: """
Expand Down Expand Up @@ -435,14 +440,7 @@ defmodule AshPhoenix.FilterForm do
{:error, %__MODULE__{} = form} ->
error =
form
|> errors()
|> Enum.map(fn
{key, message, vars} ->
"#{key}: #{AshPhoenix.replace_vars(message, vars)}"

other ->
other
end)
|> raw_errors()
|> Ash.Error.to_error_class()

raise error
Expand All @@ -456,7 +454,9 @@ defmodule AshPhoenix.FilterForm do
def to_filter!(form), do: to_filter_expression!(form)

@doc """
Returns a flat list of all errors on all predicates in the filter.
Returns a flat list of all errors on all predicates in the filter, made safe for display in a form.
Only errors that implement the `AshPhoenix.FormData.Error` protocol are displayed.
"""
def errors(form, opts \\ [])

Expand All @@ -470,6 +470,20 @@ defmodule AshPhoenix.FilterForm do
def errors(%Predicate{} = predicate, opts),
do: AshPhoenix.FilterForm.Predicate.errors(predicate, opts[:transform_errors])

@doc """
Returns a flat list of all errors on all predicates in the filter, without transforming.
"""
def raw_errors(%__MODULE__{components: components}) do
Enum.flat_map(
components,
&raw_errors(&1)
)
end

def raw_errors(%Predicate{} = predicate) do
predicate.errors
end

defp do_to_filter_expression(%__MODULE__{components: []}, _), do: {:ok, %{}}

defp do_to_filter_expression(
Expand All @@ -482,8 +496,16 @@ defmodule AshPhoenix.FilterForm do
{:ok, component_filter} ->
{filters ++ [component_filter], components ++ [component], errors?}

{:error, component} ->
{filters, components ++ [component], true}
{:error, errors} ->
{filters,
components ++
[
%{
component
| valid?: false,
errors: List.wrap(component.errors) ++ List.wrap(errors)
}
], true}
end
end)

Expand Down Expand Up @@ -519,23 +541,42 @@ defmodule AshPhoenix.FilterForm do
resource
) do
ref =
case Ash.Resource.Info.public_calculation(Ash.Resource.Info.related(resource, path), field) do
case Ash.Resource.Info.public_calculation(
Ash.Resource.Info.related(resource, path),
field
) do
nil ->
{:ok, Ash.Expr.expr(^Ash.Expr.ref(List.wrap(path), field))}

calc ->
case Ash.Query.validate_calculation_arguments(
calc,
arguments.input || %{}
) do
{:ok, input} ->
{:ok,
%Ash.Query.Call{
name: calc.name,
args: [Map.to_list(input)],
relationship_path: path
}}

%{calculation: {module, calc_opts}} = calc ->
with {:ok, input} <-
Ash.Query.validate_calculation_arguments(
calc,
arguments.input || %{}
),
{:ok, calc} <-
Ash.Query.Calculation.new(
calc.name,
module,
calc_opts,
calc.type,
calc.constraints,
arguments: input,
async?: calc.async?,
filterable?: calc.filterable?,
sortable?: calc.sortable?,
sensitive?: calc.sensitive?,
load: calc.load,
calc_name: calc.name,
source_context: %{}
) do
{:ok, %Ash.Query.Ref{
attribute: calc,
relationship_path: path,
resource: Ash.Resource.Info.related(resource, path),
input?: true
}}
else
{:error, error} ->
{:error, error}
end
Expand Down Expand Up @@ -610,7 +651,7 @@ defmodule AshPhoenix.FilterForm do
"id" => params[:id] || params["id"] || Ash.UUID.generate(),
"operator" => to_string(params[:operator] || params["operator"] || "eq"),
"negated" => params[:negated] || params["negated"] || false,
"arguments" => params["arguments"],
"arguments" => params[:arguments] || params["arguments"],
"field" => field,
"value" => params[:value] || params["value"],
"path" => path
Expand Down
2 changes: 1 addition & 1 deletion lib/ash_phoenix/filter_form/predicate.ex
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ defmodule AshPhoenix.FilterForm.Predicate do
{_key, _value, _vars} ->
true

_ ->
_unhandled ->
false
end)
|> Enum.flat_map(
Expand Down
6 changes: 6 additions & 0 deletions lib/ash_phoenix/form_data/error.ex
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ defimpl AshPhoenix.FormData.Error, for: Ash.Error.Query.InvalidArgument do
end
end

defimpl AshPhoenix.FormData.Error, for: Ash.Error.Query.InvalidCalculationArgument do
def to_form_error(error) do
{error.field, error.message, error.vars}
end
end

defimpl AshPhoenix.FormData.Error, for: Ash.Error.Changes.InvalidAttribute do
def to_form_error(error) do
{error.field, error.message, error.vars}
Expand Down
1 change: 1 addition & 0 deletions lib/ash_phoenix/plug/exception.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
errors = [
{Ash.Error.Invalid.InvalidPrimaryKey, 400},
{Ash.Error.Query.InvalidArgument, 400},
{Ash.Error.Query.InvalidCalculationArgument, 400},
{Ash.Error.Query.InvalidFilterValue, 400},
{Ash.Error.Query.NotFound, 404},
{Ash.Error.Forbidden.Policy, 403},
Expand Down
22 changes: 22 additions & 0 deletions test/filter_form_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,28 @@ defmodule AshPhoenix.FilterFormTest do
)
end

test "predicates can have arguments" do
form =
FilterForm.new(Post,
params: %{
field: :text_plus_title,
operator: :eq,
arguments: %{
delimiter: "-"
},
value: "text-title"
}
)

assert %{id: match_id} = Ash.create!(Post, %{text: "text", title: "title"})
Ash.create!(Post, %{text: "no", title: "bad"})

assert [%{id: ^match_id}] =
Post
|> FilterForm.filter!(form)
|> Ash.read!()
end

test "predicates can reference paths for to_filter_map" do
form =
FilterForm.new(Post,
Expand Down
7 changes: 7 additions & 0 deletions test/support/resources/post.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ defmodule AshPhoenix.Test.Post do
attribute(:custom_atom_field, AshPhoenix.Test.Action, public?: true)
end

calculations do
calculate :text_plus_title, :string, expr(text <> ^arg(:delimiter) <> title) do
public? true
argument :delimiter, :string, allow_nil?: false
end
end

actions do
default_accept(:*)
defaults([:read, :destroy])
Expand Down

0 comments on commit 3a50f74

Please sign in to comment.