diff --git a/lib/mayo.ex b/lib/mayo.ex index 3295a6c..db9a9a6 100644 --- a/lib/mayo.ex +++ b/lib/mayo.ex @@ -49,7 +49,7 @@ defmodule Mayo do end defp reduce_map_pipes({key, pipes}, acc) do - item = quote do: Access.get(result, unquote(key)) + item = quote do: Mayo.Access.get(result, unquote(key)) pipe = compile(pipes, item) quote do @@ -65,7 +65,7 @@ defmodule Mayo do {:error, %{err | paths: [unquote(key) | paths]}} value -> - Access.put(result, unquote(key), value) + Mayo.Access.put(result, unquote(key), value) end end end diff --git a/lib/mayo/access.ex b/lib/mayo/access.ex new file mode 100644 index 0000000..a0c668a --- /dev/null +++ b/lib/mayo/access.ex @@ -0,0 +1,33 @@ +defmodule Mayo.Access do + def fetch(value, key) when is_map(value) do + Map.fetch(value, key) + end + + def fetch(value, key) when is_list(value) do + Keyword.fetch(value, key) + end + + def get(value, key) when is_map(value) do + Map.get(value, key) + end + + def get(value, key) when is_list(value) do + Keyword.get(value, key) + end + + def put(value, key, new) when is_map(value) do + Map.put(value, key, new) + end + + def put(value, key, new) when is_list(value) do + Keyword.put(value, key, new) + end + + def delete(value, key) when is_map(value) do + Map.delete(value, key) + end + + def delete(value, key) when is_list(value) do + Keyword.delete(value, key) + end +end diff --git a/lib/mayo/map.ex b/lib/mayo/map.ex new file mode 100644 index 0000000..5393731 --- /dev/null +++ b/lib/mayo/map.ex @@ -0,0 +1,143 @@ +defmodule Mayo.Map do + @doc """ + Checks the minimum number of the keys in the map. + + iex> Mayo.Map.min(%{foo: "bar"}, 1) + %{foo: "bar"} + + iex> Mayo.Map.min(%{}, 1) + {:error, %Mayo.Error{type: "map.min"}} + """ + def min(value, len) when is_map(value) do + if length(Map.keys(value)) < len do + {:error, %Mayo.Error{ + type: "map.min" + }} + else + value + end + end + + def min(value, _), do: value + + @doc """ + Checks the maximum number of the keys in the map. + + iex> Mayo.Map.max(%{foo: "bar"}, 1) + %{foo: "bar"} + + iex> Mayo.Map.max(%{foo: "bar", baz: "boo"}, 1) + {:error, %Mayo.Error{type: "map.max"}} + """ + def max(value, len) when is_map(value) do + if length(Map.keys(value)) > len do + {:error, %Mayo.Error{ + type: "map.max" + }} + else + value + end + end + + def max(value, _), do: value + + @doc """ + Checks the number of the keys in the map. + + iex> Mayo.Map.length(%{foo: "bar"}, 1) + %{foo: "bar"} + + iex> Mayo.Map.length(%{}, 1) + {:error, %Mayo.Error{type: "map.length"}} + """ + def length(value, len) when is_map(value) do + if length(Map.keys(value)) == len do + value + else + {:error, %Mayo.Error{ + type: "map.length" + }} + end + end + + def length(value, _), do: value + + @doc """ + Checks the presence of keys. + + iex> Mayo.Map.with(%{foo: "bar", baz: "boo"}, [:foo, :baz]) + %{foo: "bar", baz: "boo"} + + iex> Mayo.Map.with(%{foo: "bar"}, [:foo, :baz]) + {:error, %Mayo.Error{type: "map.with", paths: [:baz]}} + """ + def with(value, peers) when is_map(value) do + Enum.reduce peers, value, fn key, acc -> + case acc do + {:error, _} = err -> err + + _ -> + case Map.fetch(acc, key) do + :error -> {:error, %Mayo.Error{ + paths: [key], + type: "map.with" + }} + + _ -> acc + end + end + end + end + + def with(value, _), do: value + + @doc """ + Forbids the presence of keys. + + iex> Mayo.Map.without(%{foo: "bar"}, [:baz, :boo]) + %{foo: "bar"} + + iex> Mayo.Map.without(%{foo: "bar", baz: "boo"}, [:baz, :boo]) + {:error, %Mayo.Error{type: "map.without", paths: [:baz]}} + """ + def without(value, peers) when is_map(value) do + Enum.reduce peers, value, fn key, acc -> + case acc do + {:error, _} = err -> err + + _ -> + case Map.fetch(acc, key) do + :error -> acc + + _ -> {:error, %Mayo.Error{ + paths: [key], + type: "map.without" + }} + end + end + end + end + + def without(value, _), do: value + + @doc """ + Rename a key to another name. + + iex> Mayo.Map.rename(%{foo: "bar"}, :foo, :baz) + %{baz: "bar"} + + iex> Mayo.Map.rename(%{foo: "bar"}, :bar, :baz) + %{foo: "bar"} + """ + def rename(value, from, to) when is_map(value) do + case Map.fetch(value, from) do + {:ok, result} -> + Map.put(value, to, result) + |> Map.delete(from) + + :error -> value + end + end + + def rename(value, _, _), do: value +end diff --git a/test/mayo/map_test.exs b/test/mayo/map_test.exs new file mode 100644 index 0000000..926d0e5 --- /dev/null +++ b/test/mayo/map_test.exs @@ -0,0 +1,4 @@ +defmodule Mayo.MapTest do + use ExUnit.Case + doctest Mayo.Map +end diff --git a/test/mayo_test.exs b/test/mayo_test.exs index 75f7dd1..6ccd450 100644 --- a/test/mayo_test.exs +++ b/test/mayo_test.exs @@ -3,11 +3,13 @@ defmodule MayoTest do doctest Mayo test "map" do - result = Mayo.validate %{}, %{ + result = Mayo.validate %{ + username: "test" + }, %{ username: Mayo.Any.string |> Mayo.String.min(4) } - assert result == %{} + assert result == %{username: "test"} result = Mayo.validate %{}, %{ username: Mayo.Any.string |> Mayo.Any.required |> Mayo.String.min(4) @@ -31,11 +33,13 @@ defmodule MayoTest do end test "keyword list" do - result = Mayo.validate [], [ + result = Mayo.validate [ + username: "test" + ], [ username: Mayo.Any.string |> Mayo.String.min(4) ] - assert result == [] + assert result == [username: "test"] result = Mayo.validate [], [ username: Mayo.Any.string |> Mayo.Any.required |> Mayo.String.min(4)