From efdddba3ccc7a70c61740c8d757b021413c65190 Mon Sep 17 00:00:00 2001 From: Alberto Sartori Date: Fri, 26 Jun 2020 17:23:44 +0200 Subject: [PATCH] [Change] improve errors, types and docs --- lib/ex_sieve.ex | 27 +++++++++------- lib/ex_sieve/builder.ex | 6 +++- lib/ex_sieve/builder/where.ex | 15 ++++++--- lib/ex_sieve/config.ex | 26 +++++++++++---- lib/ex_sieve/filter.ex | 26 ++++----------- lib/ex_sieve/node.ex | 18 +++++++---- lib/ex_sieve/node/attribute.ex | 9 ++++-- lib/ex_sieve/node/condition.ex | 19 ++++++----- lib/ex_sieve/node/grouping.ex | 17 +++++----- lib/ex_sieve/node/sort.ex | 16 +++++++--- lib/ex_sieve/schema.ex | 4 +-- lib/ex_sieve/utils.ex | 18 ++++++++++- test/ex_sieve/builder/where_test.exs | 46 +++++++++++++-------------- test/ex_sieve/node/attribute_test.exs | 21 ++++++++---- test/ex_sieve/node/condition_test.exs | 21 +++++++----- test/ex_sieve/node/grouping_test.exs | 5 +-- test/ex_sieve/node/sort_test.exs | 4 +-- 17 files changed, 179 insertions(+), 119 deletions(-) diff --git a/lib/ex_sieve.ex b/lib/ex_sieve.ex index ddde79a..f79f5f8 100644 --- a/lib/ex_sieve.ex +++ b/lib/ex_sieve.ex @@ -1,15 +1,9 @@ defmodule ExSieve do @moduledoc """ - ExSieve is a object query translator to Ecto.Query. - """ - - alias ExSieve.Config - - @doc """ - ExSieve is meant to be `use`d by a Ecto.Repo. + `ExSieve` is meant to be `use`d by a module implementing `Ecto.Repo` behaviour. - When `use`d, an optional default for `ignore_erros` can be provided. - If `ignore_erros` is not provided, a default of `true` will be used. + When used, optional configuration parameters can be provided. + For details about cofngiuration parameters see `t:ExSieve.Config.t/0`. defmodule MyApp.Repo do use Ecto.Repo, otp_app: :my_app @@ -21,7 +15,7 @@ defmodule ExSieve do use ExSieve, ignore_erros: true end - When `use` is called, a `filter` function is defined in the Repo. + When `use` is called, a `filter` function is defined in the Repo. """ defmacro __using__(opts) do @@ -36,8 +30,17 @@ defmodule ExSieve do end end - @typep error :: :invalid_query | :attribute_not_found | :predicate_not_found | :direction_not_found | :value_is_empty - @type result :: Ecto.Query.t() | {:error, error} + @type result :: Ecto.Query.t() | error() + + @type error :: + {:error, :invalid_query} + | {:error, {:too_deep, key :: String.t()}} + | {:error, {:predicate_not_found, key :: String.t()}} + | {:error, {:attribute_not_found, key :: String.t()}} + | {:error, {:direction_not_found, invalid_direction :: String.t()}} + | {:error, {:value_is_empty, key :: String.t()}} + | {:error, {:invalid_type, field :: String.t()}} + | {:error, {:invalid_value, {field :: String.t(), value :: any()}}} @doc """ Filters the given query based on params. diff --git a/lib/ex_sieve/builder.ex b/lib/ex_sieve/builder.ex index b57239a..474a48e 100644 --- a/lib/ex_sieve/builder.ex +++ b/lib/ex_sieve/builder.ex @@ -5,7 +5,11 @@ defmodule ExSieve.Builder do alias ExSieve.Builder.{Join, OrderBy, Where} alias ExSieve.Node.{Grouping, Sort} - @spec call(Ecto.Queryable.t(), Grouping.t(), list(Sort.t()), Config.t()) :: {:ok, Ecto.Query.t()} | {:error, any()} + @spec call(Ecto.Queryable.t(), Grouping.t(), list(Sort.t()), Config.t()) :: + {:ok, Ecto.Query.t()} + | {:error, {:predicate_not_found, predicate :: atom()}} + | {:error, {:invalid_type, field :: String.t()}} + | {:error, {:invalid_value, {field :: String.t(), value :: any()}}} def call(query, grouping, sorts, config) do with {:ok, query} <- Join.build(query, grouping, sorts), {:ok, query} <- Where.build(query, grouping, config), diff --git a/lib/ex_sieve/builder/where.ex b/lib/ex_sieve/builder/where.ex index eed6099..e91308b 100644 --- a/lib/ex_sieve/builder/where.ex +++ b/lib/ex_sieve/builder/where.ex @@ -55,7 +55,12 @@ defmodule ExSieve.Builder.Where do @spec composite_predicates :: [String.t()] def composite_predicates, do: @all_any_predicates_str - @spec build(Ecto.Queryable.t(), Grouping.t(), Config.t()) :: {:ok, Ecto.Query.t()} | {:error, any()} + @spec build(Ecto.Queryable.t(), Grouping.t(), Config.t()) :: + {:ok, Ecto.Query.t()} + | {:error, {:predicate_not_found, predicate :: atom()}} + | {:error, {:invalid_type, field :: String.t()}} + | {:error, {:invalid_value, {field :: String.t(), value :: any()}}} + def build(query, %Grouping{combinator: combinator} = grouping, config) when combinator in ~w(and or)a do case dynamic_grouping(grouping, config) do {:error, _} = err -> err @@ -124,20 +129,20 @@ defmodule ExSieve.Builder.Where do unless allowed_types == :all do defp validate_dynamic(unquote(predicate), %Attribute{type: type} = attr, _) when type not in unquote(allowed_types) do - {:error, {:invalid_type, attr}} + {:error, {:invalid_type, Utils.rebuild_key(attr)}} end end unless allowed_values == :all do defp validate_dynamic(unquote(predicate), attr, [value | _]) when value not in unquote(allowed_values) do - {:error, {:invalid_value, attr}} + {:error, {:invalid_value, {Utils.rebuild_key(attr), value}}} end end end defp validate_dynamic(predicate, _attribute, _values) when predicate in @predicates, do: :ok - defp validate_dynamic(_predicate, _attribute, _values), do: {:error, :predicate_not_found} + defp validate_dynamic(predicate, _attribute, _values), do: {:error, {:predicate_not_found, predicate}} defp build_dynamic(:eq, %Attribute{parent: [], name: name}, [value | _]) do dynamic([p], field(p, ^name) == ^value) @@ -307,7 +312,7 @@ defmodule ExSieve.Builder.Where do dynamic([{^parent_name(parent), p}], not (is_nil(field(p, ^name)) or field(p, ^name) == ^"")) end - defp build_dynamic(_predicate, _attribute, _values), do: {:error, :predicate_not_found} + defp build_dynamic(predicate, _attribute, _values), do: {:error, {:predicate_not_found, predicate}} defp escape_like_value(value), do: Regex.replace(~r/([\%_])/, value, ~S(\\\1)) end diff --git a/lib/ex_sieve/config.ex b/lib/ex_sieve/config.ex index 1fa92d2..a2092bb 100644 --- a/lib/ex_sieve/config.ex +++ b/lib/ex_sieve/config.ex @@ -3,10 +3,24 @@ defmodule ExSieve.Config do @typedoc """ `ExSieve` configuration options: - * `ignore_errors` - * `max_depth` - * `except_predicates` - * `only_predicates` + + * `:ignore_errors` - when `true` recoverable errors are ignored. Recoverable + errors include for instance missing attribute or missing preedicate, in that + case the query is returned without taking into account the filter causing the + error. Defaults to `true` + + * `:max_depth` - the maximum level of nested relations that can be queried. + Defaults to `:full` meaning no limit + + * `:only_predicates` - a list of allowed predicates. The list can contain `:basic` + and `:composite`, in that case all correpsonding predicates are added to the list. + When not given or when `nil` no limit is applied. Defaults to `nil` + + * `:except_predicates` - a list of excluded predicates. The list can contain `:basic` + and `:composite`, in that case all correpsonding predicates are added to the list. + When not given or when `nil` no limit is applied. If both `:only_predicates` and + `:except_predicates` are given `:only_predicates` takes precedence and + `:except_predicates` is ignored. Defaults to `nil` """ @type t :: %__MODULE__{ ignore_errors: boolean(), @@ -18,7 +32,7 @@ defmodule ExSieve.Config do @keys [:ignore_errors, :max_depth, :except_predicates, :only_predicates] @doc false - @spec new(Keyword.t(), call_options :: map, schema :: module()) :: ExSieve.Config.t() + @spec new(Keyword.t(), call_options :: map, schema :: module()) :: __MODULE__.t() def new(defaults, call_options, schema) do defaults = normalize_options(defaults) call_options = normalize_options(call_options) @@ -30,7 +44,7 @@ defmodule ExSieve.Config do |> Map.merge(call_options) |> Map.take(@keys) - struct(ExSieve.Config, opts) + struct(__MODULE__, opts) end defp options_from_schema(schema) do diff --git a/lib/ex_sieve/filter.ex b/lib/ex_sieve/filter.ex index f5828cb..4b5c3c8 100644 --- a/lib/ex_sieve/filter.ex +++ b/lib/ex_sieve/filter.ex @@ -4,27 +4,13 @@ defmodule ExSieve.Filter do alias ExSieve.{Builder, Config, Node, Utils} @spec filter(Ecto.Queryable.t(), %{(binary | atom) => term}, defaults :: Keyword.t(), options :: map) :: - ExSieve.result() | {:error, any()} + ExSieve.result() def filter(queryable, params, defaults \\ [], options \\ %{}) do - case Utils.extract_schema(queryable) do - {:ok, schema} -> - config = Config.new(defaults, options, schema) - - params - |> Node.call(schema, config) - |> result(queryable, config) - - {:error, _} = err -> - err - end - end - - defp result({:error, reason}, _queryable, _config), do: {:error, reason} - - defp result({:ok, groupings, sorts}, queryable, config) do - case Builder.call(queryable, groupings, sorts, config) do - {:ok, result} -> result - err -> err + with {:ok, schema} <- Utils.extract_schema(queryable), + config <- Config.new(defaults, options, schema), + {:ok, groupings, sorts} <- Node.call(params, schema, config), + {:ok, result} <- Builder.call(queryable, groupings, sorts, config) do + result end end end diff --git a/lib/ex_sieve/node.ex b/lib/ex_sieve/node.ex index 74bc865..e5cd4bb 100644 --- a/lib/ex_sieve/node.ex +++ b/lib/ex_sieve/node.ex @@ -4,14 +4,22 @@ defmodule ExSieve.Node do alias ExSieve.Node.{Grouping, Sort} alias ExSieve.{Config, Utils} - @typep error :: {:error, :attribute_not_found | :predicate_not_found | :direction_not_found} + @type error :: + {:error, {:too_deep, key :: String.t()}} + | {:error, {:predicate_not_found, key :: String.t()}} + | {:error, {:attribute_not_found, key :: String.t()}} + | {:error, {:direction_not_found, invalid_direction :: String.t()}} + | {:error, {:value_is_empty, key :: String.t()}} @spec call(%{(atom | binary) => term}, atom, Config.t()) :: {:ok, Grouping.t(), list(Sort.t())} | error def call(params_with_sort, schema, config) do params_with_sort = stringify_keys(params_with_sort) {params, sorts} = extract_sorts(params_with_sort, schema, config) - grouping = Grouping.extract(params, schema, config) - result(grouping, Utils.get_error(sorts, config)) + + with sorts when is_list(sorts) <- Utils.get_error(sorts, config), + %Grouping{} = grouping <- Grouping.extract(params, schema, config) do + {:ok, grouping, sorts} + end end defp extract_sorts(params, schema, config) do @@ -19,10 +27,6 @@ defmodule ExSieve.Node do {params, Sort.extract(sorts, schema, config)} end - defp result({:error, reason}, _sorts), do: {:error, reason} - defp result(_grouping, {:error, reason}), do: {:error, reason} - defp result(grouping, sorts), do: {:ok, grouping, sorts} - defp stringify_keys(nil), do: nil defp stringify_keys(%{__struct__: _struct} = value), do: value defp stringify_keys(%{} = map), do: Map.new(map, fn {k, v} -> {to_string(k), stringify_keys(v)} end) diff --git a/lib/ex_sieve/node/attribute.ex b/lib/ex_sieve/node/attribute.ex index 22a5d57..0c91a07 100644 --- a/lib/ex_sieve/node/attribute.ex +++ b/lib/ex_sieve/node/attribute.ex @@ -9,7 +9,10 @@ defmodule ExSieve.Node.Attribute do @type t :: %__MODULE__{} @spec extract(key :: String.t(), module | %{related: module}, Config.t()) :: - t() | {:error, :attribute_not_found | :too_deep} + t() + | {:error, {:attribute_not_found, key :: String.t()}} + | {:error, {:too_deep, key :: String.t()}} + def extract(key, module, config) do extract(key, module, {:name, get_name_and_type(module, key)}, [], config) end @@ -18,7 +21,7 @@ defmodule ExSieve.Node.Attribute do if md == :full or (is_integer(md) and length(parents) < md) do extract(key, module, {:assoc, get_assoc(module, key)}, parents, config) else - {:error, :too_deep} + {:error, {:too_deep, Utils.rebuild_key(key, parents)}} end end @@ -26,7 +29,7 @@ defmodule ExSieve.Node.Attribute do %Attribute{parent: Enum.reverse(parents), name: name, type: type} end - defp extract(_, _, {:assoc, nil}, _, _), do: {:error, :attribute_not_found} + defp extract(key, _, {:assoc, nil}, parents, _), do: {:error, {:attribute_not_found, Utils.rebuild_key(key, parents)}} defp extract(key, module, {:assoc, assoc}, parents, config) do key = String.replace_prefix(key, "#{assoc}_", "") diff --git a/lib/ex_sieve/node/condition.ex b/lib/ex_sieve/node/condition.ex index 7f0b4e5..1200526 100644 --- a/lib/ex_sieve/node/condition.ex +++ b/lib/ex_sieve/node/condition.ex @@ -12,11 +12,14 @@ defmodule ExSieve.Node.Condition do @typep values :: String.t() | integer | list(String.t() | integer) @spec extract(String.t() | atom, values, module(), Config.t()) :: - t | {:error, :predicate_not_found | :value_is_empty | :attribute_not_found} + t() + | {:error, {:predicate_not_found, key :: String.t()}} + | {:error, {:attribute_not_found, key :: String.t()}} + | {:error, {:value_is_empty, key :: String.t()}} def extract(key, values, module, config) do with {:ok, attributes} <- extract_attributes(key, module, config), {:ok, predicate} <- get_predicate(key, config), - {:ok, values} <- prepare_values(values) do + {:ok, values} <- prepare_values(values, key) do %Condition{ attributes: attributes, predicate: predicate, @@ -62,7 +65,7 @@ defmodule ExSieve.Node.Condition do |> Enum.sort_by(&byte_size/1, &>=/2) |> Enum.find(&String.ends_with?(key, &1)) |> case do - nil -> {:error, :predicate_not_found} + nil -> {:error, {:predicate_not_found, key}} predicate -> {:ok, String.to_atom(predicate)} end end @@ -75,18 +78,18 @@ defmodule ExSieve.Node.Condition do end end - defp prepare_values(values) when is_list(values) do + defp prepare_values(values, key) when is_list(values) do values - |> Enum.all?(&match?({:ok, _val}, prepare_values(&1))) + |> Enum.all?(&match?({:ok, _val}, prepare_values(&1, key))) |> if do {:ok, values} else - {:error, :value_is_empty} + {:error, {:value_is_empty, key}} end end - defp prepare_values(""), do: {:error, :value_is_empty} - defp prepare_values(value), do: {:ok, List.wrap(value)} + defp prepare_values("", key), do: {:error, {:value_is_empty, key}} + defp prepare_values(value, _key), do: {:ok, List.wrap(value)} defp replace_groups(nil, except), do: {nil, do_replace_groups(except)} defp replace_groups(only, _), do: {do_replace_groups(only), nil} diff --git a/lib/ex_sieve/node/grouping.ex b/lib/ex_sieve/node/grouping.ex index 7b22faf..38e957f 100644 --- a/lib/ex_sieve/node/grouping.ex +++ b/lib/ex_sieve/node/grouping.ex @@ -10,24 +10,25 @@ defmodule ExSieve.Node.Grouping do @combinators ~w(or and) - @spec extract(%{binary => term}, atom, Config.t()) :: t | {:error, :predicate_not_found | :value_is_empty} + @spec extract(%{binary => term}, atom, Config.t()) :: + t() + | {:error, {:attribute_not_found, key :: String.t()}} + | {:error, {:predicate_not_found, key :: String.t()}} + | {:error, {:value_is_empty, key :: String.t()}} def extract(params, schema, config) do {combinator, params} = Map.pop(params, "m", "and") {grouping, params} = Map.pop(params, "g", []) conditions = Map.get(params, "c", params) - conditions - |> do_extract(schema, config, valid_combinator(combinator)) - |> result(extract_groupings(grouping, schema, config)) + with %Grouping{} = extracted <- do_extract(conditions, schema, config, valid_combinator(combinator)), + groupings when is_list(groupings) <- extract_groupings(grouping, schema, config) do + %Grouping{extracted | groupings: groupings} + end end defp valid_combinator(combinator) when combinator in @combinators, do: String.to_atom(combinator) defp valid_combinator(_combinator), do: :and - defp result({:error, _} = err, _groupings), do: err - defp result(_grouping, {:error, _} = err), do: err - defp result(grouping, groupings), do: %Grouping{grouping | groupings: groupings} - defp do_extract(conditions, schema, config, combinator) do case extract_conditions(conditions, schema, config) do {:error, _} = err -> err diff --git a/lib/ex_sieve/node/sort.ex b/lib/ex_sieve/node/sort.ex index 4546dee..225092f 100644 --- a/lib/ex_sieve/node/sort.ex +++ b/lib/ex_sieve/node/sort.ex @@ -11,7 +11,11 @@ defmodule ExSieve.Node.Sort do @directions ~w(desc asc) @spec extract(String.t() | list(String.t()), module(), Config.t()) :: - list(t | {:error, :attribute_not_found | :direction_not_found}) + list( + t() + | {:error, {:attribute_not_found, key :: String.t()}} + | {:error, {:direction_not_found, invalid_direction :: String.t()}} + ) def extract(value, schema, %Config{} = config) when is_bitstring(value) do value |> build(schema, config) @@ -26,11 +30,15 @@ defmodule ExSieve.Node.Sort do |> result(parse_direction(value)) end - defp result(_attribute, nil), do: {:error, :direction_not_found} + defp result(_attribute, {:error, invalid_dir}), do: {:error, {:direction_not_found, invalid_dir}} defp result({:error, reason}, _direction), do: {:error, reason} - defp result(attribute, direction), do: %Sort{attribute: attribute, direction: String.to_atom(direction)} + defp result(attribute, {:ok, direction}), do: %Sort{attribute: attribute, direction: String.to_atom(direction)} defp parse_direction(value) do - value |> String.split(~r/\s+/) |> Enum.find(&Enum.member?(@directions, &1)) + case String.split(value, ~r/\s+/) do + [_, direction] when direction in @directions -> {:ok, direction} + [_, invalid_direction] -> {:error, invalid_direction} + _ -> {:error, value} + end end end diff --git a/lib/ex_sieve/schema.ex b/lib/ex_sieve/schema.ex index 4ea844e..e698c4d 100644 --- a/lib/ex_sieve/schema.ex +++ b/lib/ex_sieve/schema.ex @@ -1,6 +1,6 @@ defmodule ExSieve.Schema do @moduledoc """ - `ExSieve.Schema` is meant to be `use`d by a module using `Ecto.Schema`. + `ExSieve.Schema` is meant to be `use`d by modules using `Ecto.Schema`. When used, optional configuration parameters specific for the schema can be provided. For details about cofngiuration parameters see @@ -35,7 +35,7 @@ defmodule ExSieve.Schema do end Filters for fields that are in the list are ignored (an error is returned - if `ignore_errors` is `false`). By default all fields are filterable. + if `:ignore_errors` is `false`). By default all fields are filterable. """ defmacro __using__(opts) do diff --git a/lib/ex_sieve/utils.ex b/lib/ex_sieve/utils.ex index 8c6baee..17b6673 100644 --- a/lib/ex_sieve/utils.ex +++ b/lib/ex_sieve/utils.ex @@ -2,6 +2,7 @@ defmodule ExSieve.Utils do @moduledoc false alias ExSieve.Config + alias ExSieve.Node.Attribute @spec get_error(list(any), Config.t()) :: list(any) | {:error, atom} def get_error(items, %Config{ignore_errors: true}), do: Enum.reject(items, &match?({:error, _}, &1)) @@ -16,7 +17,7 @@ defmodule ExSieve.Utils do _ -> {:error, :invalid_query} end - @spec filter_list([any()], [any()], [any()]) :: [any()] + @spec filter_list([any()], any(), any()) :: [any()] def filter_list(list, only, except) do cond do is_list(only) -> list -- list -- only @@ -25,6 +26,21 @@ defmodule ExSieve.Utils do end end + @spec rebuild_key(Attribute.t()) :: String.t() + def rebuild_key(%Attribute{name: name, parent: parents}), do: rebuild_key(to_string(name), parents) + + @spec rebuild_key(key :: String.t(), parents :: [atom() | String.t()]) :: String.t() + def rebuild_key(key, []), do: key |> String.split() |> Enum.at(0) + + def rebuild_key(key, parents) do + parents + |> Enum.reverse() + |> Enum.map(&to_string/1) + |> Enum.join("_") + |> Kernel.<>("_#{key}") + |> rebuild_key([]) + end + defp do_extract_schema(%Ecto.Query{from: %{source: {_, schema}}}) when not is_nil(schema), do: {:ok, schema} defp do_extract_schema(_), do: {:error, :invalid_query} end diff --git a/test/ex_sieve/builder/where_test.exs b/test/ex_sieve/builder/where_test.exs index 83459d3..b9f9cc5 100644 --- a/test/ex_sieve/builder/where_test.exs +++ b/test/ex_sieve/builder/where_test.exs @@ -173,7 +173,7 @@ defmodule ExSieve.Builder.WhereTest do assert inspect(base) == ex_sieve {_base, ex_sieve} = ex_sieve_post_query(%{"id_not_cont" => 1}, false, false, false) - assert {:error, {:invalid_type, _}} = ex_sieve + assert {:error, {:invalid_type, "id"}} = ex_sieve end test ":lt" do @@ -249,7 +249,7 @@ defmodule ExSieve.Builder.WhereTest do assert inspect(base) == ex_sieve {_base, ex_sieve} = ex_sieve_post_query(%{"published_matches" => true}, false, false, false) - assert {:error, {:invalid_type, _}} = ex_sieve + assert {:error, {:invalid_type, "published"}} = ex_sieve end test ":does_not_match" do @@ -265,7 +265,7 @@ defmodule ExSieve.Builder.WhereTest do assert inspect(base) == ex_sieve {_base, ex_sieve} = ex_sieve_post_query(%{"published_at_does_not_match" => true}, false, false, false) - assert {:error, {:invalid_type, _}} = ex_sieve + assert {:error, {:invalid_type, "published_at"}} = ex_sieve end test ":start" do @@ -281,7 +281,7 @@ defmodule ExSieve.Builder.WhereTest do assert inspect(base) == ex_sieve {_base, ex_sieve} = ex_sieve_post_query(%{"published_start" => true}, false, false, false) - assert {:error, {:invalid_type, _}} = ex_sieve + assert {:error, {:invalid_type, "published"}} = ex_sieve end test ":not_start" do @@ -297,7 +297,7 @@ defmodule ExSieve.Builder.WhereTest do assert inspect(base) == ex_sieve {_base, ex_sieve} = ex_sieve_post_query(%{"published_not_start" => true}, false, false, false) - assert {:error, {:invalid_type, _}} = ex_sieve + assert {:error, {:invalid_type, "published"}} = ex_sieve end test ":end" do @@ -313,7 +313,7 @@ defmodule ExSieve.Builder.WhereTest do assert inspect(base) == ex_sieve {_base, ex_sieve} = ex_sieve_post_query(%{"published_end" => true}, false, false, false) - assert {:error, {:invalid_type, _}} = ex_sieve + assert {:error, {:invalid_type, "published"}} = ex_sieve end test ":not_end" do @@ -329,7 +329,7 @@ defmodule ExSieve.Builder.WhereTest do assert inspect(base) == ex_sieve {_base, ex_sieve} = ex_sieve_post_query(%{"published_not_end" => true}, false, false, false) - assert {:error, {:invalid_type, _}} = ex_sieve + assert {:error, {:invalid_type, "published"}} = ex_sieve end test ":true" do @@ -345,10 +345,10 @@ defmodule ExSieve.Builder.WhereTest do assert inspect(base) == ex_sieve {_base, ex_sieve} = ex_sieve_post_query(%{"title_true" => true}, false, false, false) - assert {:error, {:invalid_type, _}} = ex_sieve + assert {:error, {:invalid_type, "title"}} = ex_sieve {_base, ex_sieve} = ex_sieve_post_query(%{"published_true" => "foo"}, false, false, false) - assert {:error, {:invalid_value, _}} = ex_sieve + assert {:error, {:invalid_value, {"published", "foo"}}} = ex_sieve end test ":not_true" do @@ -364,10 +364,10 @@ defmodule ExSieve.Builder.WhereTest do assert inspect(base) == ex_sieve {_base, ex_sieve} = ex_sieve_post_query(%{"title_not_true" => true}, false, false, false) - assert {:error, {:invalid_type, _}} = ex_sieve + assert {:error, {:invalid_type, "title"}} = ex_sieve {_base, ex_sieve} = ex_sieve_post_query(%{"published_not_true" => "foo"}, false, false, false) - assert {:error, {:invalid_value, _}} = ex_sieve + assert {:error, {:invalid_value, {"published", "foo"}}} = ex_sieve end test ":false" do @@ -383,10 +383,10 @@ defmodule ExSieve.Builder.WhereTest do assert inspect(base) == ex_sieve {_base, ex_sieve} = ex_sieve_post_query(%{"title_false" => true}, false, false, false) - assert {:error, {:invalid_type, _}} = ex_sieve + assert {:error, {:invalid_type, "title"}} = ex_sieve {_base, ex_sieve} = ex_sieve_post_query(%{"published_false" => "foo"}, false, false, false) - assert {:error, {:invalid_value, _}} = ex_sieve + assert {:error, {:invalid_value, {"published", "foo"}}} = ex_sieve end test ":not_false" do @@ -402,10 +402,10 @@ defmodule ExSieve.Builder.WhereTest do assert inspect(base) == ex_sieve {_base, ex_sieve} = ex_sieve_post_query(%{"title_not_false" => true}, false, false, false) - assert {:error, {:invalid_type, _}} = ex_sieve + assert {:error, {:invalid_type, "title"}} = ex_sieve {_base, ex_sieve} = ex_sieve_post_query(%{"published_not_false" => "foo"}, false, false, false) - assert {:error, {:invalid_value, _}} = ex_sieve + assert {:error, {:invalid_value, {"published", "foo"}}} = ex_sieve end test ":blank" do @@ -421,10 +421,10 @@ defmodule ExSieve.Builder.WhereTest do assert inspect(base) == ex_sieve {_base, ex_sieve} = ex_sieve_post_query(%{"published_blank" => true}, false, false, false) - assert {:error, {:invalid_type, _}} = ex_sieve + assert {:error, {:invalid_type, "published"}} = ex_sieve {_base, ex_sieve} = ex_sieve_post_query(%{"title_blank" => "foo"}, false, false, false) - assert {:error, {:invalid_value, _}} = ex_sieve + assert {:error, {:invalid_value, {"title", "foo"}}} = ex_sieve end test ":present" do @@ -440,10 +440,10 @@ defmodule ExSieve.Builder.WhereTest do assert inspect(base) == ex_sieve {_base, ex_sieve} = ex_sieve_post_query(%{"published_present" => true}, false, false, false) - assert {:error, {:invalid_type, _}} = ex_sieve + assert {:error, {:invalid_type, "published"}} = ex_sieve {_base, ex_sieve} = ex_sieve_post_query(%{"title_present" => "foo"}, false, false, false) - assert {:error, {:invalid_value, _}} = ex_sieve + assert {:error, {:invalid_value, {"title", "foo"}}} = ex_sieve end test ":null" do @@ -456,7 +456,7 @@ defmodule ExSieve.Builder.WhereTest do assert query == ex_sieve {_base, ex_sieve} = ex_sieve_post_query(%{"title_null" => "foo"}, false, false, false) - assert {:error, {:invalid_value, _}} = ex_sieve + assert {:error, {:invalid_value, {"title", "foo"}}} = ex_sieve end test ":not_null" do @@ -469,7 +469,7 @@ defmodule ExSieve.Builder.WhereTest do assert query == ex_sieve {_base, ex_sieve} = ex_sieve_post_query(%{"title_not_null" => "foo"}, false, false, false) - assert {:error, {:invalid_value, _}} = ex_sieve + assert {:error, {:invalid_value, {"title", "foo"}}} = ex_sieve end end @@ -488,12 +488,12 @@ defmodule ExSieve.Builder.WhereTest do test "invalid_type" do {_, ex_sieve} = ex_sieve_post_query(%{"id_cont_any" => [1, 2]}, false, false, false) - assert {:error, {:invalid_type, _}} = ex_sieve + assert {:error, {:invalid_type, "id"}} = ex_sieve end test "invalid predicate" do {_, ex_sieve} = ex_sieve_post_query(%{"id_lt_all" => [1, 2]}, false, false, false) - assert {:error, :predicate_not_found} = ex_sieve + assert {:error, {:predicate_not_found, "id_lt_all"}} = ex_sieve end end end diff --git a/test/ex_sieve/node/attribute_test.exs b/test/ex_sieve/node/attribute_test.exs index 0a9650d..1706b8c 100644 --- a/test/ex_sieve/node/attribute_test.exs +++ b/test/ex_sieve/node/attribute_test.exs @@ -37,28 +37,35 @@ defmodule ExSieve.Node.AttributeTest do end test "return {:error, :attribute_not_found}" do - assert {:error, :attribute_not_found} == Attribute.extract("tid_eq", Comment, %Config{}) + assert {:error, {:attribute_not_found, "tid_eq"}} == Attribute.extract("tid_eq", Comment, %Config{}) end test "return {:error, :attribute_not_found} when parent attribute doesn't exist" do - assert {:error, :attribute_not_found} == Attribute.extract("post_tid", Comment, %Config{}) + assert {:error, {:attribute_not_found, "post_tid"}} == Attribute.extract("post_tid", Comment, %Config{}) end test "return {:error, :attribute_not_found} for not filterable field" do - assert {:error, :attribute_not_found} == Attribute.extract("inserted_at_eq", Comment, %Config{}) + assert {:error, {:attribute_not_found, "inserted_at_eq"}} == + Attribute.extract("inserted_at_eq", Comment, %Config{}) end test "return {:error, :attribute_not_found} for not filterable field in assoc" do - assert {:error, :attribute_not_found} == Attribute.extract("comments_inserted_at_eq", User, %Config{}) + assert {:error, {:attribute_not_found, "comments_inserted_at_eq"}} == + Attribute.extract("comments_inserted_at_eq", User, %Config{}) end test "return {:error, :attribute_not_found} for not filterable assoc" do - assert {:error, :attribute_not_found} == Attribute.extract("addresses_street_eq", User, %Config{}) + assert {:error, {:attribute_not_found, "addresses_street_eq"}} == + Attribute.extract("addresses_street_eq", User, %Config{}) end test "return {:error, :too_deep} when max_depth is exceeded" do - assert {:error, :too_deep} == Attribute.extract("posts_comments_body_cont", User, %Config{max_depth: 0}) - assert {:error, :too_deep} == Attribute.extract("posts_comments_body_cont", User, %Config{max_depth: 1}) + assert {:error, {:too_deep, "posts_comments_body_cont"}} == + Attribute.extract("posts_comments_body_cont", User, %Config{max_depth: 0}) + + assert {:error, {:too_deep, "posts_comments_body_cont"}} == + Attribute.extract("posts_comments_body_cont", User, %Config{max_depth: 1}) + assert %Attribute{} = Attribute.extract("posts_comments_body_cont", User, %Config{max_depth: 2}) assert %Attribute{} = Attribute.extract("posts_comments_body_cont", User, %Config{max_depth: :full}) end diff --git a/test/ex_sieve/node/condition_test.exs b/test/ex_sieve/node/condition_test.exs index 8cc5a70..1090d7e 100644 --- a/test/ex_sieve/node/condition_test.exs +++ b/test/ex_sieve/node/condition_test.exs @@ -68,32 +68,37 @@ defmodule ExSieve.Node.ConditionTest do end test "return {:error, :predicate_not_found}" do - assert {:error, :predicate_not_found} == Condition.extract("post_id_and_id", 1, Comment, %Config{}) + assert {:error, {:predicate_not_found, "post_id_and_id"}} == + Condition.extract("post_id_and_id", 1, Comment, %Config{}) end test "return {:error, :predicate_not_found} for excluded predicate" do config = %Config{ignore_errors: false, except_predicates: ["eq"]} - assert {:error, :predicate_not_found} == Condition.extract("post_id_eq", 1, Comment, config) + assert {:error, {:predicate_not_found, "post_id_eq"}} == Condition.extract("post_id_eq", 1, Comment, config) config = %Config{ignore_errors: false, except_predicates: [:composite]} - assert {:error, :predicate_not_found} == Condition.extract("body_not_cont_all", ["foo", "bar"], Comment, config) + + assert {:error, {:predicate_not_found, "body_not_cont_all"}} == + Condition.extract("body_not_cont_all", ["foo", "bar"], Comment, config) end test "return {:error, :predicate_not_found} for predicate not in only" do config = %Config{ignore_errors: false, only_predicates: ["eq"]} - assert {:error, :predicate_not_found} == Condition.extract("post_id_in", 1, Comment, config) + assert {:error, {:predicate_not_found, "post_id_in"}} == Condition.extract("post_id_in", 1, Comment, config) config = %Config{ignore_errors: false, only_predicates: [:composite]} - assert {:error, :predicate_not_found} == Condition.extract("body_cont", "foo", Comment, config) + assert {:error, {:predicate_not_found, "body_cont"}} == Condition.extract("body_cont", "foo", Comment, config) end test "return {:error, :attribute_not_found}" do - assert {:error, :attribute_not_found} == Condition.extract("tid_eq", 1, Comment, %Config{}) - assert {:error, :attribute_not_found} == Condition.extract("posts_comments_foo_eq", 1, User, %Config{}) + assert {:error, {:attribute_not_found, "tid_eq"}} == Condition.extract("tid_eq", 1, Comment, %Config{}) + + assert {:error, {:attribute_not_found, "posts_comments_foo_eq"}} == + Condition.extract("posts_comments_foo_eq", 1, User, %Config{}) end test "return {:error, :value_is_empty}" do - assert {:error, :value_is_empty} == Condition.extract("id_eq", "", Comment, %Config{}) + assert {:error, {:value_is_empty, "id_eq"}} == Condition.extract("id_eq", "", Comment, %Config{}) end end end diff --git a/test/ex_sieve/node/grouping_test.exs b/test/ex_sieve/node/grouping_test.exs index 5f69612..80fae6f 100644 --- a/test/ex_sieve/node/grouping_test.exs +++ b/test/ex_sieve/node/grouping_test.exs @@ -86,11 +86,12 @@ defmodule ExSieve.Node.GroupingTest do end test "return {:error, :predicate_not_found}", %{config: config} do - assert {:error, :predicate_not_found} == Grouping.extract(%{"post_id_and_id" => 1}, Comment, config) + assert {:error, {:predicate_not_found, "post_id_and_id"}} == + Grouping.extract(%{"post_id_and_id" => 1}, Comment, config) end test "return {:error, :attribute_not_found}", %{config: config} do - assert {:error, :attribute_not_found} == Grouping.extract(%{"tid_eq" => 1}, Comment, config) + assert {:error, {:attribute_not_found, "tid_eq"}} == Grouping.extract(%{"tid_eq" => 1}, Comment, config) end test "return nil when attribute not found and ignore_errors is true" do diff --git a/test/ex_sieve/node/sort_test.exs b/test/ex_sieve/node/sort_test.exs index 48f6755..dc5e02b 100644 --- a/test/ex_sieve/node/sort_test.exs +++ b/test/ex_sieve/node/sort_test.exs @@ -21,11 +21,11 @@ defmodule ExSieve.Node.SortTest do end test "return {:error, :direction_not_found}" do - assert [{:error, :direction_not_found}] == Sort.extract("post_body_asc", Comment, %Config{}) + assert [{:error, {:direction_not_found, "foo"}}] == Sort.extract("post_body foo", Comment, %Config{}) end test "return {:error, :attribute_not_found}" do - assert [{:error, :attribute_not_found}] == Sort.extract("tid asc", Comment, %Config{}) + assert [{:error, {:attribute_not_found, "tid"}}] == Sort.extract("tid asc", Comment, %Config{}) end end end