diff --git a/config/config.exs b/config/config.exs index 24912d3..1a11f66 100644 --- a/config/config.exs +++ b/config/config.exs @@ -1,6 +1,6 @@ # This file is responsible for configuring your application # and its dependencies with the aid of the Mix.Config module. -use Mix.Config +import Config # This configuration is loaded before any dependency and is restricted # to this project. If another project depends on this project, this @@ -30,6 +30,6 @@ use Mix.Config config :filtrex, ecto_repos: [Filtrex.Repo] -if Mix.env == :test do +if Mix.env() == :test do import_config "test.exs" end diff --git a/config/test.exs b/config/test.exs index 9fbae98..3a6ac2f 100644 --- a/config/test.exs +++ b/config/test.exs @@ -1,4 +1,4 @@ -use Mix.Config +import Config config :filtrex, Filtrex.Repo, adapter: Ecto.Adapters.Postgres, diff --git a/lib/filtrex/condition.ex b/lib/filtrex/condition.ex index 9d3be7f..f1ca043 100644 --- a/lib/filtrex/condition.ex +++ b/lib/filtrex/condition.ex @@ -15,9 +15,14 @@ defmodule Filtrex.Condition do Filtrex.Condition.Number ] - @callback parse(Filtrex.Type.Config.t, %{inverse: boolean, column: String.t, value: any, comparator: String.t}) :: {:ok, any} | {:error, any} - @callback type :: Atom.t - @callback comparators :: [String.t] + @callback parse(Filtrex.Type.Config.t(), %{ + inverse: boolean, + column: String.t(), + value: any, + comparator: String.t() + }) :: {:ok, any} | {:error, any} + @callback type :: Atom.t() + @callback comparators :: [String.t()] defstruct column: nil, comparator: nil, value: nil @@ -54,10 +59,14 @@ defmodule Filtrex.Condition do case condition_module(type) do nil -> {:error, "Unknown filter condition '#{type}'"} + module -> type_atom = String.to_existing_atom(type) - config = Filtrex.Type.Config.configs_for_type(configs, type_atom) + + config = + Filtrex.Type.Config.configs_for_type(configs, type_atom) |> Filtrex.Type.Config.config(options[:column]) + if config do module.parse(config, Map.delete(options, :type)) else @@ -68,21 +77,24 @@ defmodule Filtrex.Condition do @doc "Parses a params key into the condition type, column, and comparator" def param_key_type(configs, key_with_comparator) do - result = Enum.find_value(condition_modules(), fn (module) -> - Enum.find_value(module.comparators, fn (comparator) -> - normalized = "_" <> String.replace(comparator, " ", "_") - key = String.replace_trailing(key_with_comparator, normalized, "") - config = Filtrex.Type.Config.config(configs, key) - if !is_nil(config) and key in config.keys and config.type == module.type do - {:ok, module, config, key, comparator} - end + result = + Enum.find_value(condition_modules(), fn module -> + Enum.find_value(module.comparators(), fn comparator -> + normalized = "_" <> String.replace(comparator, " ", "_") + key = String.replace_trailing(key_with_comparator, normalized, "") + config = Filtrex.Type.Config.config(configs, key) + + if !is_nil(config) and key in config.keys and config.type == module.type() do + {:ok, module, config, key, comparator} + end + end) end) - end) + if result, do: result, else: {:error, "Unknown filter key '#{key_with_comparator}'"} end @doc "Helper method to validate that a comparator is in list" - @spec validate_comparator(atom, binary, List.t) :: {:ok, binary} | {:error, binary} + @spec validate_comparator(atom, binary, List.t()) :: {:ok, binary} | {:error, binary} def validate_comparator(type, comparator, comparators) do if comparator in comparators do {:ok, comparator} @@ -92,9 +104,10 @@ defmodule Filtrex.Condition do end @doc "Helper method to validate whether a value is in a list" - @spec validate_in(any, List.t) :: nil | any + @spec validate_in(any, List.t()) :: nil | any def validate_in(nil, _), do: nil def validate_in(_, nil), do: nil + def validate_in(value, list) do cond do value in list -> value @@ -103,32 +116,35 @@ defmodule Filtrex.Condition do end @doc "Helper method to validate whether a value is a binary" - @spec validate_is_binary(any) :: nil | String.t + @spec validate_is_binary(any) :: nil | String.t() def validate_is_binary(value) when is_binary(value), do: value def validate_is_binary(_), do: nil @doc "Generates an error description for a generic parse error" - @spec parse_error(any, Atom.t, Atom.t) :: String.t + @spec parse_error(any, Atom.t(), Atom.t()) :: String.t() def parse_error(value, type, filter_type) do "Invalid #{to_string(filter_type)} #{to_string(type)} '#{value}'" end @doc "Generates an error description for a parse error resulting from an invalid value type" - @spec parse_value_type_error(any, Atom.t) :: String.t + @spec parse_value_type_error(any, Atom.t()) :: String.t() def parse_value_type_error(column, filter_type) when is_binary(column) do "Invalid #{to_string(filter_type)} value for #{column}" end + def parse_value_type_error(column, filter_type) do - opts = struct(Inspect.Opts, []) - iodata = Inspect.Algebra.to_doc(column, opts) + opts = struct(Inspect.Opts, []) + + iodata = + Inspect.Algebra.to_doc(column, opts) |> Inspect.Algebra.format(opts.width) - |> Enum.join + |> Enum.join() if String.length(iodata) <= 15 do parse_value_type_error("'#{iodata}'", filter_type) else "'#{String.slice(iodata, 0..12)}...#{String.slice(iodata, -3..-1)}'" - |> parse_value_type_error(filter_type) + |> parse_value_type_error(filter_type) end end @@ -138,8 +154,8 @@ defmodule Filtrex.Condition do end defp condition_module(type) do - Enum.find(condition_modules(), fn (module) -> - type == to_string(module.type) + Enum.find(condition_modules(), fn module -> + type == to_string(module.type()) end) end end diff --git a/lib/filtrex/conditions/number.ex b/lib/filtrex/conditions/number.ex index 49dfe98..65134b3 100644 --- a/lib/filtrex/conditions/number.ex +++ b/lib/filtrex/conditions/number.ex @@ -20,24 +20,37 @@ defmodule Filtrex.Condition.Number do def type, do: :number - def comparators, do: [ - "equals", "does not equal", - "greater than", "less than or", - "greater than or", "less than" - ] + def comparators, + do: [ + "equals", + "does not equal", + "greater than", + "less than or", + "greater than or", + "less than" + ] def parse(config, %{column: column, comparator: comparator, value: value, inverse: inverse}) do - result = with {:ok, parsed_value} <- parse_value(config.options, value), - do: %Condition.Number{type: type(), inverse: inverse, value: parsed_value, column: column, - comparator: validate_in(comparator, comparators())} + result = + with {:ok, parsed_value} <- parse_value(config.options, value), + do: %Condition.Number{ + type: type(), + inverse: inverse, + value: parsed_value, + column: column, + comparator: validate_in(comparator, comparators()) + } case result do {:error, error} -> {:error, error} + %Condition.Number{comparator: nil} -> {:error, parse_error(column, :comparator, type())} + %Condition.Number{value: nil} -> {:error, parse_value_type_error(value, type())} + _ -> {:ok, result} end @@ -46,33 +59,37 @@ defmodule Filtrex.Condition.Number do defp parse_value(options = %{allow_decimal: true}, string) when is_binary(string) do case Float.parse(string) do {float, ""} -> parse_value(options, float) - _ -> {:error, parse_value_type_error(string, type())} + _ -> {:error, parse_value_type_error(string, type())} end end defp parse_value(options, string) when is_binary(string) do case Integer.parse(string) do {integer, ""} -> parse_value(options, integer) - _ -> {:error, parse_value_type_error(string, type())} + _ -> {:error, parse_value_type_error(string, type())} end end defp parse_value(options, float) when is_float(float) do allowed_values = options[:allowed_values] + cond do options[:allow_decimal] == false -> {:error, parse_value_type_error(float, type())} + allowed_values == nil -> {:ok, float} - Range.range?(allowed_values) -> - start..final = allowed_values + + start..final//_ = allowed_values -> if float >= start and float <= final do {:ok, float} else {:error, "Provided number value not allowed"} end + is_list(allowed_values) and float in allowed_values -> {:ok, float} + is_list(allowed_values) and float not in allowed_values -> {:error, "Provided number value not allowed"} end @@ -80,9 +97,11 @@ defmodule Filtrex.Condition.Number do defp parse_value(options, integer) when is_integer(integer) do allowed_values = options[:allowed_values] + cond do allowed_values == nil or integer in allowed_values -> {:ok, integer} + integer not in allowed_values -> {:error, "Provided number value not allowed"} end @@ -91,11 +110,11 @@ defmodule Filtrex.Condition.Number do defp parse_value(_, value), do: {:error, parse_value_type_error(value, type())} defimpl Filtrex.Encoder do - encoder "equals", "does not equal", "column = ?" - encoder "does not equal", "equals", "column != ?" - encoder "greater than", "less than or", "column > ?" - encoder "less than or", "greater than", "column <= ?" - encoder "less than", "greater than or", "column < ?" - encoder "greater than or", "less than", "column >= ?" + encoder("equals", "does not equal", "column = ?") + encoder("does not equal", "equals", "column != ?") + encoder("greater than", "less than or", "column > ?") + encoder("less than or", "greater than", "column <= ?") + encoder("less than", "greater than or", "column < ?") + encoder("greater than or", "less than", "column >= ?") end end diff --git a/lib/filtrex/config.ex b/lib/filtrex/config.ex index 8644f26..87f5207 100644 --- a/lib/filtrex/config.ex +++ b/lib/filtrex/config.ex @@ -61,21 +61,29 @@ defmodule Filtrex.Type.Config do end end - for module <- Filtrex.Condition.condition_modules do - @doc "Generate a config struct for `#{to_string(module) |> String.slice(7..-1)}`" - defmacro unquote(module.type)(key_or_keys, opts \\ []) - defmacro unquote(module.type)(keys, opts) when is_list(keys) do - type = unquote(module.type) + for module <- Filtrex.Condition.condition_modules() do + @doc "Generate a config struct for `#{to_string(module) |> String.slice(7..-1//1)}`" + defmacro unquote(module.type())(key_or_keys, opts \\ []) + + defmacro unquote(module.type())(keys, opts) when is_list(keys) do + type = unquote(module.type()) + quote do - var!(configs) = var!(configs) ++ - [%Filtrex.Type.Config{type: unquote(type), - keys: Filtrex.Type.Config.to_strings(unquote(keys)), - options: Enum.into(unquote(opts), %{})}] + var!(configs) = + var!(configs) ++ + [ + %Filtrex.Type.Config{ + type: unquote(type), + keys: Filtrex.Type.Config.to_strings(unquote(keys)), + options: Enum.into(unquote(opts), %{}) + } + ] end end - defmacro unquote(module.type)(key, opts) do - type = unquote(module.type) + defmacro unquote(module.type())(key, opts) do + type = unquote(module.type()) + quote do unquote(type)([to_string(unquote(key))], unquote(opts)) end @@ -84,11 +92,14 @@ defmodule Filtrex.Type.Config do @doc "Convert a list of mixed atoms and/or strings to a list of strings" def to_strings(keys, strings \\ []) + def to_strings([key | keys], strings) when is_atom(key) do to_strings(keys, strings ++ [to_string(key)]) end + def to_strings([key | keys], strings) when is_binary(key) do to_strings(keys, strings ++ [key]) end + def to_strings([], strings), do: strings end diff --git a/mix.exs b/mix.exs index b99c834..6572afd 100644 --- a/mix.exs +++ b/mix.exs @@ -36,14 +36,14 @@ defmodule Filtrex.Mixfile do defp deps do [ {:postgrex, ">= 0.0.0", only: :test}, - {:ecto, "~> 3.0"}, - {:ecto_sql, "~> 3.0"}, - {:timex, "~> 3.1"}, - {:earmark, "~> 0.1", only: :dev}, - {:ex_doc, "~> 0.11", only: :dev}, + {:ecto, "~> 3.12.5"}, + {:ecto_sql, "~> 3.12.1"}, + {:timex, "~> 3.7.11"}, + {:earmark, "~> 1.4.17", only: :dev}, + {:ex_doc, "~> 0.35", only: :dev}, {:inch_ex, ">= 0.0.0", only: [:dev, :docs]}, - {:plug, "~> 1.1.2", only: :test}, - {:ex_machina, "~> 0.6.1", only: :test}, + {:plug, "~> 1.16.1", only: :test}, + {:ex_machina, "~> 2.8.0", only: :test}, {:mix_test_watch, "~> 0.3", only: :dev, runtime: false} ] end diff --git a/mix.lock b/mix.lock index 5c75c83..ba6450c 100644 --- a/mix.lock +++ b/mix.lock @@ -1,28 +1,38 @@ %{ - "certifi": {:hex, :certifi, "0.7.0", "861a57f3808f7eb0c2d1802afeaae0fa5de813b0df0979153cbafcd853ababaf", [:rebar3], [], "hexpm"}, - "combine": {:hex, :combine, "0.9.6", "8d1034a127d4cbf6924c8a5010d3534d958085575fa4d9b878f200d79ac78335", [:mix], [], "hexpm"}, - "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"}, - "db_connection": {:hex, :db_connection, "2.0.3", "b4e8aa43c100e16f122ccd6798cd51c48c79fd391c39d411f42b3cd765daccb0", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm"}, - "decimal": {:hex, :decimal, "1.6.0", "bfd84d90ff966e1f5d4370bdd3943432d8f65f07d3bab48001aebd7030590dcc", [:mix], [], "hexpm"}, - "earmark": {:hex, :earmark, "0.2.1", "ba6d26ceb16106d069b289df66751734802777a3cbb6787026dd800ffeb850f3", [:mix], [], "hexpm"}, - "ecto": {:hex, :ecto, "3.0.4", "5d0e2b89baaa03eac37ec49f9018c39a4e2fb6501dc3ff5a839de742e171a09f", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"}, - "ecto_sql": {:hex, :ecto_sql, "3.0.3", "dd17f2401a69bb2ec91d5564bd259ad0bc63ee32c2cb2e616d04f1559801dba6", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.0.4", [hex: :ecto, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.9.1", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.14.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.2.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"}, - "ex_doc": {:hex, :ex_doc, "0.12.0", "b774aabfede4af31c0301aece12371cbd25995a21bb3d71d66f5c2fe074c603f", [:mix], [{:earmark, "~> 0.2", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"}, - "ex_machina": {:hex, :ex_machina, "0.6.2", "2d25802d269b21ecb3df478c3609f3b162ef6d1c952d75770e0969f8971611de", [:mix], [], "hexpm"}, - "fs": {:hex, :fs, "0.9.2", "ed17036c26c3f70ac49781ed9220a50c36775c6ca2cf8182d123b6566e49ec59", [:rebar], [], "hexpm"}, - "gettext": {:hex, :gettext, "0.13.0", "daafbddc5cda12738bb93b01d84105fe75b916a302f1c50ab9fb066b95ec9db4", [:mix], [], "hexpm"}, - "hackney": {:hex, :hackney, "1.6.3", "d489d7ca2d4323e307bedc4bfe684323a7bf773ecfd77938f3ee8074e488e140", [:mix, :rebar3], [{:certifi, "0.7.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "1.2.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, - "idna": {:hex, :idna, "1.2.0", "ac62ee99da068f43c50dc69acf700e03a62a348360126260e87f2b54eced86b2", [:rebar3], [], "hexpm"}, - "inch_ex": {:hex, :inch_ex, "0.5.5", "b63f57e281467bd3456461525fdbc9e158c8edbe603da6e3e4671befde796a3d", [:mix], [{:poison, "~> 1.5 or ~> 2.0 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, - "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"}, - "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"}, - "mix_test_watch": {:hex, :mix_test_watch, "0.4.1", "a98a84c795623f1ba020324f4354cf30e7120ba4dab65f9c2ae300f830a25f75", [:mix], [{:fs, "~> 0.9.1", [hex: :fs, repo: "hexpm", optional: false]}], "hexpm"}, - "plug": {:hex, :plug, "1.1.6", "8927e4028433fcb859e000b9389ee9c37c80eb28378eeeea31b0273350bf668b", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: true]}], "hexpm"}, - "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, + "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, + "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, + "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm", "4a0850c9be22a43af9920a71ab17c051f5f7d45c209e40269a1938832510e4d9"}, + "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, + "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, + "earmark": {:hex, :earmark, "1.4.47", "7e7596b84fe4ebeb8751e14cbaeaf4d7a0237708f2ce43630cfd9065551f94ca", [:mix], [], "hexpm", "3e96bebea2c2d95f3b346a7ff22285bc68a99fbabdad9b655aa9c6be06c698f8"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"}, + "ecto": {:hex, :ecto, "3.12.5", "4a312960ce612e17337e7cefcf9be45b95a3be6b36b6f94dfb3d8c361d631866", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6eb18e80bef8bb57e17f5a7f068a1719fbda384d40fc37acb8eb8aeca493b6ea"}, + "ecto_sql": {:hex, :ecto_sql, "3.12.1", "c0d0d60e85d9ff4631f12bafa454bc392ce8b9ec83531a412c12a0d415a3a4d0", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aff5b958a899762c5f09028c847569f7dfb9cc9d63bdb8133bff8a5546de6bf5"}, + "ex_doc": {:hex, :ex_doc, "0.35.1", "de804c590d3df2d9d5b8aec77d758b00c814b356119b3d4455e4b8a8687aecaf", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "2121c6402c8d44b05622677b761371a759143b958c6c19f6558ff64d0aed40df"}, + "ex_machina": {:hex, :ex_machina, "2.8.0", "a0e847b5712065055ec3255840e2c78ef9366634d62390839d4880483be38abe", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "79fe1a9c64c0c1c1fab6c4fa5d871682cb90de5885320c187d117004627a7729"}, + "expo": {:hex, :expo, "1.1.0", "f7b9ed7fb5745ebe1eeedf3d6f29226c5dd52897ac67c0f8af62a07e661e5c75", [:mix], [], "hexpm", "fbadf93f4700fb44c331362177bdca9eeb8097e8b0ef525c9cc501cb9917c960"}, + "fs": {:hex, :fs, "0.9.2", "ed17036c26c3f70ac49781ed9220a50c36775c6ca2cf8182d123b6566e49ec59", [:rebar], [], "hexpm", "9a00246e8af58cdf465ae7c48fd6fd7ba2e43300413dfcc25447ecd3bf76f0c1"}, + "gettext": {:hex, :gettext, "0.26.2", "5978aa7b21fada6deabf1f6341ddba50bc69c999e812211903b169799208f2a8", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "aa978504bcf76511efdc22d580ba08e2279caab1066b76bb9aa81c4a1e0a32a5"}, + "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"}, + "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, + "inch_ex": {:hex, :inch_ex, "0.5.5", "b63f57e281467bd3456461525fdbc9e158c8edbe603da6e3e4671befde796a3d", [:mix], [{:poison, "~> 1.5 or ~> 2.0 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm", "9c2e35eb4189db5fe3448d1e2b98b0802a3e83a63e39e137c04d09ef1450f636"}, + "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, + "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, + "makeup_erlang": {:hex, :makeup_erlang, "1.0.1", "c7f58c120b2b5aa5fd80d540a89fdf866ed42f1f3994e4fe189abebeab610839", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "8a89a1eeccc2d798d6ea15496a6e4870b75e014d1af514b1b71fa33134f57814"}, + "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, + "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, + "mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"}, + "mix_test_watch": {:hex, :mix_test_watch, "0.4.1", "a98a84c795623f1ba020324f4354cf30e7120ba4dab65f9c2ae300f830a25f75", [:mix], [{:fs, "~> 0.9.1", [hex: :fs, repo: "hexpm", optional: false]}], "hexpm", "2b8d732595cf29b960328744fb46f5592d4b2605ce4f8bebdd12d0ab57d35a41"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, + "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, + "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"}, + "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"}, + "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm", "fec8660eb7733ee4117b85f55799fd3833eb769a6df71ccf8903e8dc5447cfce"}, "poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], [], "hexpm"}, - "postgrex": {:hex, :postgrex, "0.14.1", "63247d4a5ad6b9de57a0bac5d807e1c32d41e39c04b8a4156a26c63bcd8a2e49", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, - "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"}, - "telemetry": {:hex, :telemetry, "0.2.0", "5b40caa3efe4deb30fb12d7cd8ed4f556f6d6bd15c374c2366772161311ce377", [:mix], [], "hexpm"}, - "timex": {:hex, :timex, "3.1.7", "71f9c32e13ff4860e86a314303757cc02b3ead5db6e977579a2935225ce9a666", [:mix], [{:combine, "~> 0.7", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"}, - "tzdata": {:hex, :tzdata, "0.5.10", "087e8dfe8c0283473115ad8ca6974b898ecb55ca5c725427a142a79593391e90", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, + "postgrex": {:hex, :postgrex, "0.19.3", "a0bda6e3bc75ec07fca5b0a89bffd242ca209a4822a9533e7d3e84ee80707e19", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d31c28053655b78f47f948c85bb1cf86a9c1f8ead346ba1aa0d0df017fa05b61"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, + "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, + "timex": {:hex, :timex, "3.7.11", "bb95cb4eb1d06e27346325de506bcc6c30f9c6dea40d1ebe390b262fad1862d1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8b9024f7efbabaf9bd7aa04f65cf8dcd7c9818ca5737677c7b76acbc6a94d1aa"}, + "tzdata": {:hex, :tzdata, "1.1.2", "45e5f1fcf8729525ec27c65e163be5b3d247ab1702581a94674e008413eef50b", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "cec7b286e608371602318c414f344941d5eb0375e14cfdab605cca2fe66cba8b"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, } diff --git a/test/ast_test.exs b/test/ast_test.exs index 0dad544..a621217 100644 --- a/test/ast_test.exs +++ b/test/ast_test.exs @@ -2,22 +2,56 @@ defmodule FiltrexASTTest do use ExUnit.Case use Timex - @filter %Filtrex{type: "any", conditions: [ - %Filtrex.Condition.Text{column: "title", comparator: "contains", value: "created"}, - %Filtrex.Condition.Text{column: "title", comparator: "does not equal", value: "Chris McCord"} - ], sub_filters: [ - %Filtrex{type: "all", conditions: [ - %Filtrex.Condition.Date{column: "date_column", comparator: "after", value: Timex.to_date({2016, 5, 1})}, - %Filtrex.Condition.Date{column: "date_column", comparator: "before", value: Timex.to_date({2017, 1, 1})} - ]} - ]} + @filter %Filtrex{ + type: "any", + conditions: [ + %Filtrex.Condition.Text{column: "title", comparator: "contains", value: "created"}, + %Filtrex.Condition.Text{ + column: "title", + comparator: "does not equal", + value: "Chris McCord" + } + ], + sub_filters: [ + %Filtrex{ + type: "all", + conditions: [ + %Filtrex.Condition.Date{ + column: "date_column", + comparator: "after", + value: Timex.to_date({2016, 5, 1}) + }, + %Filtrex.Condition.Date{ + column: "date_column", + comparator: "before", + value: Timex.to_date({2017, 1, 1}) + } + ] + } + ] + } test "building an ecto query expression" do ast = Filtrex.AST.build_query(Filtrex.SampleModel, @filter) expression = Macro.to_string(quote do: unquote(ast)) + assert with_newline(expression) == """ - Ecto.Query.where(Filtrex.SampleModel, [s], fragment("((lower(?) LIKE lower(?)) OR (? != ?)) OR ((? > ?) AND (? < ?))", s.title(), "%created%", s.title(), "Chris McCord", s.date_column(), "2016-05-01", s.date_column(), "2017-01-01")) - """ + Ecto.Query.where( + Filtrex.SampleModel, + [s], + fragment( + \"((lower(?) LIKE lower(?)) OR (? != ?)) OR ((? > ?) AND (? < ?))\", + s.title, + \"%created%\", + s.title, + \"Chris McCord\", + s.date_column, + \"2016-05-01\", + s.date_column, + \"2017-01-01\" + ) + ) + """ end defp with_newline(string), do: "#{string}\n" diff --git a/test/conditions/date_test.exs b/test/conditions/date_test.exs index f3c58a0..aac02d0 100644 --- a/test/conditions/date_test.exs +++ b/test/conditions/date_test.exs @@ -11,76 +11,102 @@ defmodule FiltrexConditionDateTest do test "parsing errors with binary date format" do assert Date.parse(@config, %{ - inverse: false, - column: @column, - value: "2015-09-34", - comparator: "after" - }) == {:error, "Invalid date value format: Expected `day of month` at line 1, column 9."} + inverse: false, + column: @column, + value: "2015-09-34", + comparator: "after" + }) == + {:error, "Invalid date value format: Expected `day of month` at line 1, column 9."} assert Date.parse(@config, %{ - inverse: false, - column: @column, - value: %{start: "2015-03-01"}, - comparator: "after" - }) == {:error, "Invalid date value for '%{start: \"201...1\"}'"} + inverse: false, + column: @column, + value: %{start: "2015-03-01"}, + comparator: "after" + }) == {:error, "Invalid date value for '%{start: \"201...1\"}'"} end test "parsing errors with start/end date format" do assert Date.parse(@config, %{ - inverse: false, - column: @column, - value: %{start: "2015-03-01"}, - comparator: "between" - }) == {:error, "Invalid date value format: Both a start and end key are required."} + inverse: false, + column: @column, + value: %{start: "2015-03-01"}, + comparator: "between" + }) == {:error, "Invalid date value format: Both a start and end key are required."} assert Date.parse(@config, %{ - inverse: false, - column: @column, - value: %{start: "2015-03-01", end: "2015-13-21"}, - comparator: "between" - }) == {:error, "Invalid date value format: Expected `1-2 digit month` at line 1, column 6."} + inverse: false, + column: @column, + value: %{start: "2015-03-01", end: "2015-13-21"}, + comparator: "between" + }) == + {:error, "Invalid date value format: Expected `2 digit month` at line 1, column 6."} end test "specifying different date formats" do assert Date.parse(@options_config, %{ - inverse: false, - column: @column, - value: "12-29-2016", - comparator: "after" - }) == {:ok, %Filtrex.Condition.Date{column: "date_column", comparator: "after", - inverse: false, type: :date, value: Timex.to_date({2016, 12, 29})}} + inverse: false, + column: @column, + value: "12-29-2016", + comparator: "after" + }) == + {:ok, + %Filtrex.Condition.Date{ + column: "date_column", + comparator: "after", + inverse: false, + type: :date, + value: Timex.to_date({2016, 12, 29}) + }} end test "'equals' comparator" do assert Date.parse(@config, %{ - inverse: false, - column: @column, - value: "2016-05-18", - comparator: "equals" - }) |> elem(0) == :ok + inverse: false, + column: @column, + value: "2016-05-18", + comparator: "equals" + }) + |> elem(0) == :ok end test "encoding as SQL fragments for ecto" do - assert encode(Date, @column, @default, "after") == {"? > ?", [column_ref(:date_column), @default]} - assert encode(Date, @column, @default, "on or after") == {"? >= ?", [column_ref(:date_column), @default]} - assert encode(Date, @column, @default, "before") == {"? < ?", [column_ref(:date_column), @default]} - assert encode(Date, @column, @default, "on or before") == {"? <= ?", [column_ref(:date_column), @default]} + assert encode(Date, @column, @default, "after") == + {"? > ?", [column_ref(:date_column), @default]} + + assert encode(Date, @column, @default, "on or after") == + {"? >= ?", [column_ref(:date_column), @default]} + + assert encode(Date, @column, @default, "before") == + {"? < ?", [column_ref(:date_column), @default]} + + assert encode(Date, @column, @default, "on or before") == + {"? <= ?", [column_ref(:date_column), @default]} assert encode(Date, @column, %{start: @default, end: "2015-12-31"}, "between") == - {"(? >= ?) AND (? <= ?)", [column_ref(:date_column), @default, column_ref(:date_column), "2015-12-31"]} + {"(? >= ?) AND (? <= ?)", + [column_ref(:date_column), @default, column_ref(:date_column), "2015-12-31"]} assert encode(Date, @column, %{start: @default, end: "2015-12-31"}, "not between") == - {"(? > ?) AND (? < ?)", [column_ref(:date_column), "2015-12-31", column_ref(:date_column), @default]} + {"(? > ?) AND (? < ?)", + [column_ref(:date_column), "2015-12-31", column_ref(:date_column), @default]} assert encode(Date, @column, "2016-03-01", "equals") == - {"? = ?", [column_ref(:date_column), "2016-03-01"]} + {"? = ?", [column_ref(:date_column), "2016-03-01"]} assert encode(Date, @column, "2016-03-01", "does not equal") == - {"? != ?", [column_ref(:date_column), "2016-03-01"]} + {"? != ?", [column_ref(:date_column), "2016-03-01"]} end defp encode(module, column, value, comparator) do - {:ok, condition} = module.parse(@config, %{inverse: false, column: column, value: value, comparator: comparator}) + {:ok, condition} = + module.parse(@config, %{ + inverse: false, + column: column, + value: value, + comparator: comparator + }) + encoded = Filtrex.Encoder.encode(condition) {encoded.expression, encoded.values} end diff --git a/test/support/factories/condition_params.ex b/test/support/factories/condition_params.ex index aed50f6..4ce849b 100644 --- a/test/support/factories/condition_params.ex +++ b/test/support/factories/condition_params.ex @@ -1,23 +1,28 @@ defmodule Factory.ConditionParams do use ExMachina - def factory(:text) do + def text_factory do %{type: "text", column: "title", comparator: "equals", value: "earth"} end - def factory(:date) do + def date_factory do %{type: "date", column: "date_column", comparator: "equals", value: "2015-03-01"} end - def factory(:datetime) do - %{type: "datetime", column: "datetime_column", comparator: "equals", value: "2016-04-02T13:00:00.000Z"} + def datetime_factory do + %{ + type: "datetime", + column: "datetime_column", + comparator: "equals", + value: "2016-04-02T13:00:00.000Z" + } end - def factory(:number_rating) do + def number_rating_factory do %{type: "number", column: "rating", comparator: "equals", value: 0} end - def factory(:number_upvotes) do + def number_upvotes_factory do %{type: "number", column: "upvotes", comparator: "equals", value: 0} end @@ -33,8 +38,8 @@ defmodule Factory.ConditionParams do Map.put(condition, :comparator, comparator) end - def equals(condition), do: comparator(condition, "equals") + def equals(condition), do: comparator(condition, "equals") def does_not_equal(condition), do: comparator(condition, "does not equal") - def on_or_after(condition), do: comparator(condition, "on or after") - def on_or_before(condition), do: comparator(condition, "on or before") + def on_or_after(condition), do: comparator(condition, "on or after") + def on_or_before(condition), do: comparator(condition, "on or before") end diff --git a/test/support/factories/filter_params.ex b/test/support/factories/filter_params.ex index dc68433..e4636c6 100644 --- a/test/support/factories/filter_params.ex +++ b/test/support/factories/filter_params.ex @@ -1,15 +1,15 @@ defmodule Factory.FilterParams do use ExMachina - def factory(:all) do + def all_factory do %{filter: %{type: "all", conditions: []}} end - def factory(:any) do + def any_factory do %{filter: %{type: "any", conditions: []}} end - def factory(:none) do + def none_factory do %{filter: %{type: "none", conditions: []}} end