diff --git a/lib/ash/error/query/invalid_sort.ex b/lib/ash/error/query/invalid_sort.ex new file mode 100644 index 000000000..3b2f71ef3 --- /dev/null +++ b/lib/ash/error/query/invalid_sort.ex @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2019 ash contributors +# +# SPDX-License-Identifier: MIT + +defmodule Ash.Error.Query.InvalidSort do + @moduledoc "Used when an invalid sort is provided" + + use Splode.Error, fields: [:sort], class: :invalid + + def message(%{sort: sort}) do + "#{inspect(sort)} is not a valid sort" + end +end diff --git a/lib/ash/sort/sort.ex b/lib/ash/sort/sort.ex index 34ae76de7..4674695a9 100644 --- a/lib/ash/sort/sort.ex +++ b/lib/ash/sort/sort.ex @@ -22,7 +22,7 @@ defmodule Ash.Sort do list(sort_item) | sort_item - alias Ash.Error.Query.{InvalidSortOrder, NoSuchField, UnsortableField} + alias Ash.Error.Query.{InvalidSort, InvalidSortOrder, NoSuchField, UnsortableField} @doc """ Builds an expression to be used in a sort statement. Prefer to use `Ash.Expr.calc/2` instead. @@ -125,6 +125,8 @@ defmodule Ash.Sort do def parse_input(_resource, nil, _), do: {:ok, nil} + def parse_input(_resource, sort, _), do: {:error, InvalidSort.exception(sort: sort)} + @doc """ Same as `parse_input/2` except raises any errors @@ -211,13 +213,16 @@ defmodule Ash.Sort do end end - def parse_sort(resource, field, handler, public_only?) do + def parse_sort(resource, field, handler, public_only?) + when is_binary(field) or is_atom(field) or is_struct(field) do case get_field(resource, field, handler, public_only?) do {:error, error} -> {:error, error} {:ok, field} -> {:ok, add_order(field, :asc)} end end + def parse_sort(_resource, sort, _, _), do: {:error, InvalidSort.exception(sort: sort)} + defp add_order({field, map}, order) when is_map(map) do {field, {map, order}} end diff --git a/test/sort/sort_test.exs b/test/sort/sort_test.exs index 3f1044322..ad1b2aba7 100644 --- a/test/sort/sort_test.exs +++ b/test/sort/sort_test.exs @@ -204,6 +204,20 @@ defmodule Ash.Test.Sort.SortTest do assert %{sort: [title: :asc, contents: :desc]} = Ash.Query.sort_input(Post, ["+title", "-contents"]) end + + test "a sort that is neither a string nor a list adds an error to the query" do + for input <- [5, %{}, %{"title" => "asc"}] do + assert %Ash.Query{valid?: false, errors: [%Ash.Error.Query.InvalidSort{sort: ^input}]} = + Ash.Query.sort_input(Post, input) + end + end + + test "a list containing a non-field value adds an error to the query" do + assert %Ash.Query{ + valid?: false, + errors: [%Ash.Error.Query.InvalidSort{sort: %{"field" => "title"}}] + } = Ash.Query.sort_input(Post, [%{"field" => "title"}]) + end end describe "sorting on calculations whose expression/2 reads context.source_context" do @@ -298,6 +312,13 @@ defmodule Ash.Test.Sort.SortTest do assert {:error, %Ash.Error.Query.NoSuchField{}} = Ash.Sort.parse_input(Post, "points") end + test "a sort that is neither a string nor a list is an error" do + assert {:error, %Ash.Error.Query.InvalidSort{sort: 5}} = Ash.Sort.parse_input(Post, 5) + + assert {:error, %Ash.Error.Query.InvalidSort{sort: %{"title" => "asc"}}} = + Ash.Sort.parse_input(Post, %{"title" => "asc"}) + end + test "a list sort parses properly" do assert {:ok, [title: :asc, contents: :desc]} = Ash.Sort.parse_input(Post, ["title", "-contents"])