Skip to content

Instantly share code, notes, and snippets.

@fertapric
Created May 13, 2018 07:53
Show Gist options
  • Save fertapric/427505e866a16c5073cee086683a13b3 to your computer and use it in GitHub Desktop.
Save fertapric/427505e866a16c5073cee086683a13b3 to your computer and use it in GitHub Desktop.
defmodule Params do
@moduledoc """
A set of functions for working with parameters.
Parameters are maps that impose restrictions on the key type:
* all the keys must be atoms or strings.
* all the keys must be of the same type.
Otherwise, all the functions of this module will raise `Params.MixedKeysError`.
"""
@type t :: %{required(String.t()) => any} | %{required(atom) => any}
@type key :: atom | String.t()
defguard is_key(value) when is_binary(value) or is_atom(value)
defmodule MixedKeysError do
defexception [:message, :params]
def exception(params) do
%MixedKeysError{
message:
"expected params to be a map with atoms or string keys, " <>
"got a map with mixed keys: #{inspect(params)}",
params: params
}
end
end
@doc """
Deletes the entry in `params` for a specific `key`.
If `:key` or `"key"` does not exist, returns `params` unchanged.
## Examples
iex> Params.delete(%{a: 1, b: 2}, :a)
%{b: 2}
iex> Params.delete(%{a: 1, b: 2}, "a")
%{b: 2}
iex> Params.delete(%{"a" => 1, "b" => 2}, :a)
%{"b" => 2}
iex> Params.delete(%{"a" => 1, "b" => 2}, "a")
%{"b" => 2}
iex> Params.delete(%{b: 2}, :a)
%{b: 2}
"""
@spec delete(t, key) :: t
def delete(%{} = params, key) when is_key(key) do
cond do
all_atoms?(params) -> Map.delete(params, :"#{key}")
all_strings?(params) -> Map.delete(params, "#{key}")
true -> raise MixedKeysError, params
end
end
@doc """
Drops the given `keys` from `params`.
If `keys` contains keys (`:key` or `"key"`) that are not in `params`,
they're simply ignored.
## Examples
iex> Params.drop(%{a: 1, b: 2, c: 3}, [:b, "c", "d"])
%{a: 1}
iex> Params.drop(%{"a" => 1, "b" => 2, "c" => 3}, [:b, :c, "d"])
%{"a" => 1}
"""
@spec drop(t, [key]) :: t
def drop(%{} = params, keys) when is_list(keys), do: do_drop(params, keys)
def drop(%{} = params, %MapSet{} = keys), do: do_drop(params, keys)
defp do_drop(%{} = params, keys), do: Enum.reduce(keys, params, &delete(&2, &1))
@doc """
Checks if two params are equivalent.
Two params are considered to be equivalent if they contain equivalent
keys (`:key ~ "key"`) and those keys contain the same values.
## Examples
iex> Params.equivalent?(%{a: 1, b: 2}, %{b: 2, a: 1})
true
iex> Params.equivalent?(%{a: 1, b: 2}, %{"b" => 2, "a" => 1})
true
iex> Params.equivalent?(%{a: 1, b: 2}, %{b: 1, a: 2})
false
iex> Params.equivalent?(%{"a" => 1, "b" => 2}, %{b: 1, a: 2})
false
"""
@spec equivalent?(t, t) :: boolean
def equivalent?(%{} = params1, %{} = params2) do
equivalent_keys?(params1, params2) and equivalent_keys?(params2, params1)
end
defp equivalent_keys?(params1, params2) do
unless all_atoms?(params1) or all_strings?(params1), do: raise(MixedKeysError, params1)
Enum.all?(params1, fn {key, value} ->
case fetch(params2, key) do
{:ok, ^value} -> true
_ -> false
end
end)
end
@doc """
Fetches the value for a specific `key` in the given `params`.
If `params` contains `:key` or `"key"` with value `value`, then
`{:ok, value}` is returned. If `params` doesn’t contain `key`,
`:error` is returned.
## Examples
iex> Params.fetch(%{a: 1, b: 2}, :a)
{:ok, 1}
iex> Params.fetch(%{a: 1, b: 2}, "a")
{:ok, 1}
iex> Params.fetch(%{"a" => 1, "b" => 2}, :a)
{:ok, 1}
iex> Params.fetch(%{"a" => 1, "b" => 2}, "a")
{:ok, 1}
iex> Params.fetch(%{a: 1, b: 2}, :c)
:error
"""
@spec fetch(t, key) :: {:ok, any} | :error
def fetch(%{} = params, key) when is_key(key) do
cond do
all_atoms?(params) -> Map.fetch(params, :"#{key}")
all_strings?(params) -> Map.fetch(params, "#{key}")
true -> raise MixedKeysError, params
end
end
@doc """
Fetches the value for a specific `key` in the given `params`,
erroring out if `params` doesn’t contain `key`.
If `params` contains `:key` or `"key"`, the corresponding value is returned.
If `params` doesn’t contain `key`, a `KeyError` exception is raised.
## Examples
iex> Params.fetch!(%{a: 1, b: 2}, :a)
1
iex> Params.fetch!(%{a: 1, b: 2}, "a")
1
iex> Params.fetch!(%{"a" => 1, "b" => 2}, :a)
1
iex> Params.fetch!(%{"a" => 1, "b" => 2}, "a")
1
iex> Params.fetch!(%{a: 1, b: 2}, :c)
** (KeyError) key :c not found in: %{a: 1, b: 2}
"""
@spec fetch!(t, key) :: any | no_return
def fetch!(%{} = params, key) when is_key(key) do
cond do
all_atoms?(params) -> Map.fetch!(params, :"#{key}")
all_strings?(params) -> Map.fetch!(params, "#{key}")
true -> raise MixedKeysError, params
end
end
@doc """
Filters `params`, i.e. returns only those elements for which `fun` returns
a truthy value.
See also `reject/2` which discards all elements where the function returns true.
## Examples
iex> Params.filter(%{a: 1, b: nil}, fn _k, v -> not is_nil(v) end)
%{a: 1}
iex> Params.filter(%{"a": 1, "b": nil}, fn _k, v -> not is_nil(v) end)
%{"a": 1}
"""
@spec filter(t, (key, any -> as_boolean(term))) :: t
def filter(%{} = params, fun) when is_function(fun, 2) do
if all_atoms?(params) or all_strings?(params) do
params
|> Enum.filter(fn {key, value} -> fun.(key, value) end)
|> Enum.into(%{})
else
raise MixedKeysError, params
end
end
@doc """
Gets the value for a specific `key` in `params`.
If `:key` or `"key"` is present in `params` with value `value`,
then `value` is returned. Otherwise, `default` is returned (which
is `nil` unless specified otherwise).
## Examples
iex> Params.get(%{}, :a)
nil
iex> Params.get(%{a: 1}, :a)
1
iex> Params.get(%{a: 1}, "a")
1
iex> Params.get(%{"a" => 1}, :a)
1
iex> Params.get(%{"a" => 1}, "a")
1
iex> Params.get(%{a: 1}, :b)
nil
iex> Params.get(%{a: 1}, :b, 3)
3
"""
@spec get(t, key, any) :: any
def get(%{} = params, key, default \\ nil) when is_key(key) do
cond do
all_atoms?(params) -> Map.get(params, :"#{key}", default)
all_strings?(params) -> Map.get(params, "#{key}", default)
true -> raise MixedKeysError, params
end
end
@doc """
Gets the value from `key` and updates it, all in one pass.
`fun` is called with the current value under `key` in `params` (or `nil`
if `:key` or `"key"` are not present in `params`) and must return a
two-element tuple: the “get” value (the retrieved value, which can be
operated on before being returned) and the new value to be stored under
`key` in the resulting new params. `fun` may also return `:pop`, which
means the current value shall be removed from `params` and returned
(making this function behave like `Params.pop(params, key)`).
The returned value is a tuple with the “get” value returned by `fun` and
a new map with the updated value under `key`.
## Examples
iex> Params.get_and_update(%{a: 1}, :a, fn current_value ->
...> {current_value, "new value!"}
...> end)
{1, %{a: "new value!"}}
iex> Params.get_and_update(%{a: 1}, "a", &{&1, 2})
{1, %{a: 2}}
iex> Params.get_and_update(%{"a" => 1}, :a, &{&1, 2})
{1, %{"a" => 2}}
iex> Params.get_and_update(%{"a" => 1}, :a, &{&1, 2})
{1, %{"a" => 2}}
iex> Params.get_and_update(%{a: 1}, :b, fn current_value ->
...> {current_value, "new value!"}
...> end)
{nil, %{b: "new value!", a: 1}}
iex> Params.get_and_update(%{a: 1}, "b", &{&1, 2})
{nil, %{a: 1, b: 2}}
iex> Params.get_and_update(%{"a" => 1}, :b, &{&1, 2})
{nil, %{"a" => 1, "b" => 2}}
iex> Params.get_and_update(%{"a" => 1}, :b, &{&1, 2})
{nil, %{"a" => 1, "b" => 2}}
iex> Params.get_and_update(%{a: 1}, :a, fn _ -> :pop end)
{1, %{}}
iex> Params.get_and_update(%{a: 1}, :b, fn _ -> :pop end)
{nil, %{a: 1}}
"""
@spec get_and_update(t, key, (any -> {any, any} | :pop)) :: {any, t}
def get_and_update(%{} = params, key, fun) when is_key(key) and is_function(fun, 1) do
cond do
all_atoms?(params) -> Map.get_and_update(params, :"#{key}", fun)
all_strings?(params) -> Map.get_and_update(params, "#{key}", fun)
true -> raise MixedKeysError, params
end
end
@doc """
Gets the value from `key` and updates it. Raises if there is no `:key`
nor `"key"`.
Behaves exactly like `get_and_update/3`, but raises a `KeyError` exception
if `:key` nor `"key"` are not present in `params`.
## Examples
iex> Params.get_and_update!(%{a: 1}, :a, fn current_value ->
...> {current_value, "new value!"}
...> end)
{1, %{a: "new value!"}}
iex> Params.get_and_update!(%{a: 1}, "a", &{&1, 2})
{1, %{a: 2}}
iex> Params.get_and_update!(%{"a" => 1}, :a, &{&1, 2})
{1, %{"a" => 2}}
iex> Params.get_and_update!(%{"a" => 1}, "a", &{&1, 2})
{1, %{"a" => 2}}
iex> Params.get_and_update!(%{a: 1}, :b, fn current_value ->
...> {current_value, "new value!"}
...> end)
** (KeyError) key :b not found in: %{a: 1}
iex> Params.get_and_update!(%{a: 1}, :a, fn _ ->
...> :pop
...> end)
{1, %{}}
"""
@spec get_and_update!(t, key, (any -> {any, any} | :pop)) :: any
def get_and_update!(%{} = params, key, fun) when is_key(key) and is_function(fun, 1) do
cond do
all_atoms?(params) -> Map.get_and_update!(params, :"#{key}", fun)
all_strings?(params) -> Map.get_and_update!(params, "#{key}", fun)
true -> raise MixedKeysError, params
end
end
@doc """
Gets the value for a specific `key` in `params`.
If `:key` or `"key"` are present in `params` with value `value`,
then value is returned. Otherwise, `fun` is evaluated and its result
is returned.
This is useful if the default value is very expensive to calculate or
generally difficult to setup and teardown again.
## Examples
iex> Params.get_lazy(%{a: 1}, :a, fn ->
...> # some expensive operation here
...> 13
...> end)
1
iex> Params.get_lazy(%{a: 1}, "a", fn -> 13 end)
1
iex> Params.get_lazy(%{"a" => 1}, :a, fn -> 13 end)
1
iex> Params.get_lazy(%{"a" => 1}, "a", fn -> 13 end)
1
iex> Params.get_lazy(%{a: 1}, :b, fn -> 13 end)
13
"""
@spec get_lazy(t, key, (() -> any)) :: any
def get_lazy(%{} = params, key, fun) when is_key(key) and is_function(fun, 0) do
cond do
all_atoms?(params) -> Map.get_lazy(params, :"#{key}", fun)
all_strings?(params) -> Map.get_lazy(params, "#{key}", fun)
true -> raise MixedKeysError, params
end
end
@doc """
Returns whether `:key` or `"key"` exists in the given `params`.
## Examples
iex> Params.has_key?(%{a: 1}, :a)
true
iex> Params.has_key?(%{a: 1}, "a")
true
iex> Params.has_key?(%{"a" => 1}, :a)
true
iex> Params.has_key?(%{"a" => 1}, "a")
true
iex> Params.has_key?(%{a: 1}, :b)
false
"""
@spec has_key?(t, key) :: boolean
def has_key?(%{} = params, key) when is_key(key) do
cond do
all_atoms?(params) -> Map.has_key?(params, :"#{key}")
all_strings?(params) -> Map.has_key?(params, "#{key}")
true -> raise MixedKeysError, params
end
end
@doc """
Returns whether all `keys` exist in the given `params`.
## Examples
iex> Params.has_keys?(%{a: 1, b: 2}, [:a, "b"])
true
iex> Params.has_keys?(%{"a" => 1, "b" => 2}, [:a, "b"])
true
iex> Params.has_keys?(%{a: 1}, [:a, "b"])
false
"""
@spec has_keys?(t, [key]) :: boolean
def has_keys?(%{} = params, keys) when is_list(keys), do: do_has_keys?(params, keys)
def has_keys?(%{} = params, %MapSet{} = keys), do: do_has_keys?(params, keys)
defp do_has_keys?(%{} = params, keys), do: Enum.all?(keys, &has_key?(params, &1))
@doc """
Merges two params into one.
All keys in `params2` will be added to `params1`, overriding any existing
one (i.e., the keys in `params2` "have precedence" over the ones in `params1`).
## Examples
iex> Params.merge(%{a: 1, b: 2}, %{a: 3, d: 4})
%{a: 3, b: 2, d: 4}
iex> Params.merge(%{a: 1, b: 2}, %{"a" => 3, "d" => 4})
%{a: 3, b: 2, d: 4}
iex> Params.merge(%{"a" => 1, "b" => 2}, %{a: 3, d: 4})
%{"a" => 3, "b" => 2, "d" => 4}
iex> Params.merge(%{"a" => 1, "b" => 2}, %{"a" => 3, "d" => 4})
%{"a" => 3, "b" => 2, "d" => 4}
"""
@spec merge(t, t) :: t
def merge(%{} = params1, %{} = params2) do
unless all_atoms?(params2) or all_strings?(params2), do: raise(MixedKeysError, params2)
cond do
all_atoms?(params1) ->
Enum.reduce(params2, params1, fn {key, value}, params1 ->
Map.put(params1, :"#{key}", value)
end)
all_strings?(params1) ->
Enum.reduce(params2, params1, fn {key, value}, params1 ->
Map.put(params1, "#{key}", value)
end)
true ->
raise MixedKeysError, params1
end
end
@doc """
Merges two params into one, resolving conflicts through the given `fun`.
All keys in `params2` will be added to `params1`. The given function will
be invoked when there are duplicate keys; its arguments are `key` (the
duplicate key), `value1` (the value of `key` in `params1`), and `value2`
(the value of `key` in `params2`). The value returned by `fun` is used as
the value under `key` in the resulting params.
## Examples
iex> Params.merge(%{a: 1, b: 2}, %{"a" => 3, "d" => 4}, fn :a, v1, v2 ->
...> v1 + v2
...> end)
%{a: 4, b: 2, d: 4}
iex> Params.merge(%{"a" => 1, "b" => 2}, %{a: 3, d: 4}, fn "a", v1, v2 ->
...> v1 + v2
...> end)
%{"a" => 4, "b" => 2, "d" => 4}
"""
@spec merge(t, t, (key, any, any -> any)) :: t
def merge(%{} = params1, %{} = params2, fun) when is_function(fun, 3) do
unless all_atoms?(params2) or all_strings?(params2), do: raise(MixedKeysError, params2)
key_fun =
cond do
all_atoms?(params1) -> fn key -> :"#{key}" end
all_strings?(params1) -> fn key -> "#{key}" end
true -> raise MixedKeysError, params1
end
Enum.reduce(params2, params1, fn {key, value2}, params1 ->
param_key = key_fun.(key)
case Map.fetch(params1, param_key) do
{:ok, value1} -> Map.put(params1, param_key, fun.(param_key, value1, value2))
:error -> Map.put(params1, param_key, value2)
end
end)
end
@doc """
Returns and removes the value associated with `key` in `params`.
If `:key` or `"key"` are present in `params` with value `value`,
`{value, new_params}` is returned where `new_params` is the result
of removing `key` from `params`. If `key` is not present in `params`,
`{default, params}` is returned.
## Examples
iex> Params.pop(%{a: 1, b: 2}, :a)
{1, %{b: 2}}
iex> Params.pop(%{a: 1, b: 2}, "a")
{1, %{b: 2}}
iex> Params.pop(%{"a" => 1, "b" => 2}, :a)
{1, %{"b" => 2}}
iex> Params.pop(%{"a" => 1, "b" => 2}, "a")
{1, %{"b" => 2}}
iex> Params.pop(%{a: 1}, :b)
{nil, %{a: 1}}
iex> Params.pop(%{a: 1}, "b")
{nil, %{a: 1}}
iex> Params.pop(%{"a" => 1}, :b)
{nil, %{"a" => 1}}
iex> Params.pop(%{"a" => 1}, "b")
{nil, %{"a" => 1}}
iex> Params.pop(%{a: 1}, "b", 3)
{3, %{a: 1}}
"""
@spec pop(t, key, any) :: {any, t}
def pop(%{} = params, key, default \\ nil) when is_key(key) do
cond do
all_atoms?(params) -> Map.pop(params, :"#{key}", default)
all_strings?(params) -> Map.pop(params, "#{key}", default)
true -> raise MixedKeysError, params
end
end
@doc """
Lazily returns and removes the value associated with `key` in `params`.
If `:key` or `"key"` are present in `params` with value `value`,
`{value, new_params}` is returned where `new_params` is the result of
removing `key` from `params`. If `key` is not present in `params`,
`{fun_result, params}` is returned, where `fun_result` is the result of
applying `fun`.
This is useful if the default value is very expensive to calculate or
generally difficult to setup and teardown again.
## Examples
iex> Params.pop_lazy(%{a: 1, b: 2}, :a, fn ->
...> # some expensive operation here
...> 13
...> end)
{1, %{b: 2}}
iex> Params.pop_lazy(%{a: 1, b: 2}, "a", fn -> 13 end)
{1, %{b: 2}}
iex> Params.pop_lazy(%{"a" => 1, "b" => 2}, :a, fn -> 13 end)
{1, %{"b" => 2}}
iex> Params.pop_lazy(%{"a" => 1, "b" => 2}, "a", fn -> 13 end)
{1, %{"b" => 2}}
iex> Params.pop_lazy(%{a: 1, b: 2}, :c, fn -> 13 end)
{13, %{a: 1, b: 2}}
iex> Params.pop_lazy(%{a: 1, b: 2}, "c", fn -> 13 end)
{13, %{a: 1, b: 2}}
iex> Params.pop_lazy(%{"a" => 1, "b" => 2}, :c, fn -> 13 end)
{13, %{"a" => 1, "b" => 2}}
iex> Params.pop_lazy(%{"a" => 1, "b" => 2}, "c", fn -> 13 end)
{13, %{"a" => 1, "b" => 2}}
"""
@spec pop_lazy(t, key, (() -> any)) :: {any, t}
def pop_lazy(%{} = params, key, fun) when is_key(key) and is_function(fun, 0) do
cond do
all_atoms?(params) -> Map.pop_lazy(params, :"#{key}", fun)
all_strings?(params) -> Map.pop_lazy(params, "#{key}", fun)
true -> raise MixedKeysError, params
end
end
@doc """
Puts the given `value` under `key` in `params`.
## Examples
iex> Params.put(%{a: 1}, :b, 2)
%{a: 1, b: 2}
iex> Params.put(%{a: 1}, "b", 2)
%{a: 1, b: 2}
iex> Params.put(%{"a" => 1}, :b, 2)
%{"a" => 1, "b" => 2}
iex> Params.put(%{"a" => 1}, "b", 2)
%{"a" => 1, "b" => 2}
iex> Params.put(%{a: 1, b: 2}, :a, 3)
%{a: 3, b: 2}
iex> Params.put(%{a: 1, b: 2}, "a", 3)
%{a: 3, b: 2}
iex> Params.put(%{"a" => 1, "b" => 2}, :a, 3)
%{"a" => 3, "b" => 2}
iex> Params.put(%{"a" => 1, "b" => 2}, "a", 3)
%{"a" => 3, "b" => 2}
"""
@spec put(t, key, any) :: t
def put(%{} = params, key, value) when is_key(key) do
cond do
all_atoms?(params) -> Map.put(params, :"#{key}", value)
all_strings?(params) -> Map.put(params, "#{key}", value)
true -> raise MixedKeysError, params
end
end
@doc """
Puts the given `value` under `key` unless the entry `key` already exists
in `params`.
## Examples
iex> Params.put_new(%{a: 1}, :b, 2)
%{a: 1, b: 2}
iex> Params.put_new(%{a: 1}, "b", 2)
%{a: 1, b: 2}
iex> Params.put_new(%{"a" => 1}, :b, 2)
%{"a" => 1, "b" => 2}
iex> Params.put_new(%{"a" => 1}, "b", 2)
%{"a" => 1, "b" => 2}
iex> Params.put_new(%{a: 1, b: 2}, :a, 3)
%{a: 1, b: 2}
iex> Params.put_new(%{a: 1, b: 2}, "a", 3)
%{a: 1, b: 2}
iex> Params.put_new(%{"a" => 1, "b" => 2}, :a, 3)
%{"a" => 1, "b" => 2}
iex> Params.put_new(%{"a" => 1, "b" => 2}, "a", 3)
%{"a" => 1, "b" => 2}
"""
@spec put_new(t, key, any) :: t
def put_new(%{} = params, key, value) when is_key(key) do
cond do
all_atoms?(params) -> Map.put_new(params, :"#{key}", value)
all_strings?(params) -> Map.put_new(params, "#{key}", value)
true -> raise MixedKeysError, params
end
end
@doc """
Evaluates `fun` and puts the result under `key` in `params` unless `key`
is already present.
This function is useful in case you want to compute the value to put under
`key` only if `key` is not already present (e.g., the value is expensive to
calculate or generally difficult to setup and teardown again).
## Examples
iex> Params.put_new_lazy(%{a: 1}, :a, fn ->
...> # some expensive operation here
...> 3
...> end)
%{a: 1}
iex> Params.put_new_lazy(%{a: 1}, "a", fn -> 3 end)
%{a: 1}
iex> Params.put_new_lazy(%{"a" => 1}, :a, fn -> 3 end)
%{"a" => 1}
iex> Params.put_new_lazy(%{"a" => 1}, "a", fn -> 3 end)
%{"a" => 1}
iex> Params.put_new_lazy(%{a: 1}, :b, fn -> 3 end)
%{a: 1, b: 3}
iex> Params.put_new_lazy(%{a: 1}, "b", fn -> 3 end)
%{a: 1, b: 3}
iex> Params.put_new_lazy(%{"a" => 1}, :b, fn -> 3 end)
%{"a" => 1, "b" => 3}
iex> Params.put_new_lazy(%{"a" => 1}, "b", fn -> 3 end)
%{"a" => 1, "b" => 3}
"""
@spec put_new_lazy(t, key, (() -> any)) :: t
def put_new_lazy(%{} = params, key, fun) when is_key(key) and is_function(fun, 0) do
cond do
all_atoms?(params) -> Map.put_new_lazy(params, :"#{key}", fun)
all_strings?(params) -> Map.put_new_lazy(params, "#{key}", fun)
true -> raise MixedKeysError, params
end
end
@doc """
Returns elements of `params` for which the function `fun` returns `false`
or `nil`.
See also `filter/2`.
## Examples
iex> Params.reject(%{a: 1, b: nil}, fn _key, value -> is_nil(value) end)
%{a: 1}
iex> Params.reject(%{"a": 1, "b": nil}, fn _key, value -> is_nil(value) end)
%{"a": 1}
"""
@spec reject(t, (key, any -> as_boolean(term))) :: t
def reject(%{} = params, fun) when is_function(fun, 2) do
if all_atoms?(params) or all_strings?(params) do
params
|> Enum.reject(fn {key, value} -> fun.(key, value) end)
|> Enum.into(%{})
else
raise MixedKeysError, params
end
end
@doc """
Alters the value stored under `key` to `value`, but only if the entry `key`
already exists in `params`.
If `:key` nor `"key"` are not present in `params`, a `KeyError` exception
is raised.
## Examples
iex> Params.replace!(%{a: 1, b: 2}, :a, 3)
%{a: 3, b: 2}
iex> Params.replace!(%{a: 1, b: 2}, "a", 3)
%{a: 3, b: 2}
iex> Params.replace!(%{"a" => 1, "b" => 2}, :a, 3)
%{"a" => 3, "b" => 2}
iex> Params.replace!(%{"a" => 1, "b" => 2}, "a", 3)
%{"a" => 3, "b" => 2}
iex> Params.replace!(%{a: 1}, "b", 2)
** (KeyError) key :b not found in: %{a: 1}
"""
@spec replace!(t, key, any) :: t
def replace!(%{} = params, key, value) when is_key(key) do
cond do
all_atoms?(params) -> Map.replace!(params, :"#{key}", value)
all_strings?(params) -> Map.replace!(params, "#{key}", value)
true -> raise MixedKeysError, params
end
end
@doc """
Takes all entries corresponding to the given `keys` in `params` and
extracts them into a separate `params`.
Returns a tuple with the new params and the old params with removed keys.
Keys for which there are no entries in `params` are ignored.
## Examples
iex> Params.split(%{a: 1, b: 2, c: 3}, [:a, "c", :e, "d"])
{%{a: 1, c: 3}, %{b: 2}}
iex> Params.split(%{"a" => 1, "b" => 2, "c" => 3}, [:a, "c", :e, "d"])
{%{"a" => 1, "c" => 3}, %{"b" => 2}}
"""
@spec split(t, [key]) :: {t, t}
def split(%{} = params, keys) when is_list(keys), do: do_split(params, keys)
def split(%{} = params, %MapSet{} = keys), do: do_split(params, keys)
defp do_split(%{} = params, keys) do
cond do
all_atoms?(params) -> Map.split(params, Enum.map(keys, fn key -> :"#{key}" end))
all_strings?(params) -> Map.split(params, Enum.map(keys, fn key -> "#{key}" end))
true -> raise MixedKeysError, params
end
end
@doc """
Returns new params with all the key-value pairs in `params` where the key is
in `keys`.
If `keys` contains keys that are not in `params`, they’re simply ignored.
## Examples
iex> Params.take(%{a: 1, b: 2, c: 3}, [:a, "c", :e, "d"])
%{a: 1, c: 3}
iex> Params.take(%{"a" => 1, "b" => 2, "c" => 3}, [:a, "c", :e, "d"])
%{"a" => 1, "c" => 3}
"""
@spec take(t, [key]) :: t
def take(%{} = params, keys) when is_list(keys), do: do_take(params, keys)
def take(%{} = params, %MapSet{} = keys), do: do_take(params, keys)
defp do_take(%{} = params, keys) do
cond do
all_atoms?(params) -> Map.take(params, Enum.map(keys, fn key -> :"#{key}" end))
all_strings?(params) -> Map.take(params, Enum.map(keys, fn key -> "#{key}" end))
true -> raise MixedKeysError, params
end
end
@doc """
Updates the `key` in `params` with the given function.
If `:key` or `"key"` are present in `params` with value `value`, `fun` is
invoked with argument `value` and its result is used as the new value of
`key`. If `key` is not present in `params`, `initial` is inserted as the
value of `key`. The initial value will not be passed through the update
function.
## Examples
iex> Params.update(%{a: 1}, :a, 13, &(&1 * 2))
%{a: 2}
iex> Params.update(%{a: 1}, "a", 13, &(&1 * 2))
%{a: 2}
iex> Params.update(%{"a" => 1}, :a, 13, &(&1 * 2))
%{"a" => 2}
iex> Params.update(%{"a" => 1}, "a", 13, &(&1 * 2))
%{"a" => 2}
iex> Params.update(%{a: 1}, :b, 11, &(&1 * 2))
%{a: 1, b: 11}
iex> Params.update(%{a: 1}, "b", 11, &(&1 * 2))
%{a: 1, b: 11}
iex> Params.update(%{"a" => 1}, :b, 11, &(&1 * 2))
%{"a" => 1, "b" => 11}
iex> Params.update(%{"a" => 1}, "b", 11, &(&1 * 2))
%{"a" => 1, "b" => 11}
"""
@spec update(t, key, any, (any -> any)) :: t
def update(%{} = params, key, initial, fun) when is_key(key) and is_function(fun, 1) do
cond do
all_atoms?(params) -> Map.update(params, :"#{key}", initial, fun)
all_strings?(params) -> Map.update(params, "#{key}", initial, fun)
true -> raise MixedKeysError, params
end
end
@doc """
Updates `key` with the given function.
If `:key` or `"key"` are present in `params` with value `value`, `fun` is
invoked with argument `value` and its result is used as the new value of
`key`. If `key` is not present in `params`, a `KeyError` exception is raised.
## Examples
iex> Params.update!(%{a: 1}, :a, &(&1 * 2))
%{a: 2}
iex> Params.update!(%{a: 1}, "a", &(&1 * 2))
%{a: 2}
iex> Params.update!(%{"a" => 1}, :a, &(&1 * 2))
%{"a" => 2}
iex> Params.update!(%{"a" => 1}, "a", &(&1 * 2))
%{"a" => 2}
iex> Params.update!(%{a: 1}, "b", &(&1 * 2))
** (KeyError) key :b not found in: %{a: 1}
"""
@spec update!(t, key, (any -> any)) :: t
def update!(%{} = params, key, fun) when is_key(key) and is_function(fun, 1) do
cond do
all_atoms?(params) -> Map.update!(params, :"#{key}", fun)
all_strings?(params) -> Map.update!(params, "#{key}", fun)
true -> raise MixedKeysError, params
end
end
defp all_atoms?(params), do: Enum.all?(params, fn {field, _value} -> is_atom(field) end)
defp all_strings?(params), do: Enum.all?(params, fn {field, _value} -> is_binary(field) end)
end
defmodule ParamsTest do
use ExUnit.Case, async: true
@invalid_key 1
@invalid_keys 1
@invalid_params []
doctest Params
test "delete/2 raises Params.MixedKeysError with params with mixed keys" do
message =
"expected params to be a map with atoms or string keys, " <>
"got a map with mixed keys: %{:b => 2, \"a\" => 1}"
assert_raise Params.MixedKeysError, message, fn ->
Params.delete(%{"a" => 1, b: 2}, :a)
end
end
test "delete/2 raises FunctionClauseError with invalid args" do
assert_raise FunctionClauseError, fn ->
Params.delete(@invalid_params, :a)
end
assert_raise FunctionClauseError, fn ->
Params.delete(%{}, @invalid_key)
end
end
test "drop/2 raises Params.MixedKeysError with params with mixed keys" do
message =
"expected params to be a map with atoms or string keys, " <>
"got a map with mixed keys: %{:b => 2, \"a\" => 1}"
assert_raise Params.MixedKeysError, message, fn ->
Params.drop(%{"a" => 1, b: 2}, [:a, :b])
end
end
test "drop/2 works with sets" do
assert Params.drop(%{a: 1, b: 2, c: 3}, MapSet.new([:b, "c"])) == %{a: 1}
assert Params.drop(%{"a" => 1, "b" => 2, "c" => 3}, MapSet.new([:b, "c"])) == %{"a" => 1}
end
test "drop/2 works with empty keys" do
assert Params.drop(%{a: 1, b: 2, c: 3}, []) == %{a: 1, b: 2, c: 3}
assert Params.drop(%{"a" => 1, "b" => 2, "c" => 3}, []) == %{"a" => 1, "b" => 2, "c" => 3}
end
test "drop/2 raises FunctionClauseError with invalid args" do
assert_raise FunctionClauseError, fn ->
Params.drop(@invalid_params, [:a])
end
assert_raise FunctionClauseError, fn ->
Params.drop(%{}, @invalid_keys)
end
end
test "equivalent?/2 raises Params.MixedKeysError with params with mixed keys" do
message =
"expected params to be a map with atoms or string keys, " <>
"got a map with mixed keys: %{:b => 2, \"a\" => 1}"
assert_raise Params.MixedKeysError, message, fn ->
Params.equivalent?(%{"a" => 1, b: 2}, %{})
end
assert_raise Params.MixedKeysError, message, fn ->
Params.equivalent?(%{}, %{"a" => 1, b: 2})
end
end
test "equivalent?/2 raises FunctionClauseError with invalid args" do
assert_raise FunctionClauseError, fn ->
Params.equivalent?(@invalid_params, %{})
end
assert_raise FunctionClauseError, fn ->
Params.equivalent?(%{}, @invalid_params)
end
end
test "fetch/2 raises Params.MixedKeysError with params with mixed keys" do
message =
"expected params to be a map with atoms or string keys, " <>
"got a map with mixed keys: %{:b => 2, \"a\" => 1}"
assert_raise Params.MixedKeysError, message, fn ->
Params.fetch(%{"a" => 1, b: 2}, :a)
end
end
test "fetch/2 does not return :error with nil values" do
assert Params.fetch(%{a: nil, b: 2}, :a) == {:ok, nil}
end
test "fetch/2 raises FunctionClauseError with invalid args" do
assert_raise FunctionClauseError, fn ->
Params.fetch(@invalid_params, :a)
end
assert_raise FunctionClauseError, fn ->
Params.fetch(%{}, @invalid_key)
end
end
test "fetch!/2 raises Params.MixedKeysError with params with mixed keys" do
message =
"expected params to be a map with atoms or string keys, " <>
"got a map with mixed keys: %{:b => 2, \"a\" => 1}"
assert_raise Params.MixedKeysError, message, fn ->
Params.fetch!(%{"a" => 1, b: 2}, :a)
end
end
test "fetch!/2 does not return :error with nil values" do
assert Params.fetch!(%{a: nil, b: 2}, :a) == nil
end
test "fetch!/2 raises FunctionClauseError with invalid args" do
assert_raise FunctionClauseError, fn ->
Params.fetch!(@invalid_params, :a)
end
assert_raise FunctionClauseError, fn ->
Params.fetch!(%{}, @invalid_key)
end
end
test "filter/2 raises Params.MixedKeysError with params with mixed keys" do
message =
"expected params to be a map with atoms or string keys, " <>
"got a map with mixed keys: %{:b => 2, \"a\" => 1}"
assert_raise Params.MixedKeysError, message, fn ->
Params.filter(%{"a" => 1, b: 2}, fn _key, value -> not is_nil(value) end)
end
end
test "filter/2 raises FunctionClauseError with invalid args" do
assert_raise FunctionClauseError, fn ->
Params.filter(@invalid_params, fn key, value -> value end)
end
assert_raise FunctionClauseError, fn ->
invalid_fun = fn -> {1, 1} end
Params.filter(%{}, invalid_fun)
end
end
test "get/3 raises Params.MixedKeysError with params with mixed keys" do
message =
"expected params to be a map with atoms or string keys, " <>
"got a map with mixed keys: %{:b => 2, \"a\" => 1}"
assert_raise Params.MixedKeysError, message, fn ->
Params.get(%{"a" => 1, b: 2}, :a)
end
end
test "get/3 raises FunctionClauseError with invalid args" do
assert_raise FunctionClauseError, fn ->
Params.get(@invalid_params, :a)
end
assert_raise FunctionClauseError, fn ->
Params.get(%{}, @invalid_key)
end
end
test "get_and_update/3 raises Params.MixedKeysError with params with mixed keys" do
message =
"expected params to be a map with atoms or string keys, " <>
"got a map with mixed keys: %{:b => 2, \"a\" => 1}"
assert_raise Params.MixedKeysError, message, fn ->
Params.get_and_update(%{"a" => 1, b: 2}, :a, fn value -> {value, value} end)
end
end
test "get_and_update/3 raises FunctionClauseError with invalid args" do
assert_raise FunctionClauseError, fn ->
Params.get_and_update(@invalid_params, :a, fn value -> {value, value} end)
end
assert_raise FunctionClauseError, fn ->
Params.get_and_update(%{}, @invalid_key, fn value -> {value, value} end)
end
assert_raise FunctionClauseError, fn ->
invalid_fun = fn -> {1, 1} end
Params.get_and_update(%{}, :a, invalid_fun)
end
end
test "get_and_update/3 with invalid function" do
message = "the given function must return a two-element tuple or :pop, got: 1"
assert_raise RuntimeError, message, fn ->
Params.get_and_update(%{a: 1}, :a, fn value -> value end)
end
end
test "get_and_update!/3 raises Params.MixedKeysError with params with mixed keys" do
message =
"expected params to be a map with atoms or string keys, " <>
"got a map with mixed keys: %{:b => 2, \"a\" => 1}"
assert_raise Params.MixedKeysError, message, fn ->
Params.get_and_update!(%{"a" => 1, b: 2}, :a, fn value -> {value, value} end)
end
end
test "get_and_update!/3 raises FunctionClauseError with invalid args" do
assert_raise FunctionClauseError, fn ->
Params.get_and_update!(@invalid_params, :a, fn value -> {value, value} end)
end
assert_raise FunctionClauseError, fn ->
Params.get_and_update!(%{}, @invalid_key, fn value -> {value, value} end)
end
assert_raise FunctionClauseError, fn ->
invalid_fun = fn -> {1, 1} end
Params.get_and_update!(%{}, :a, invalid_fun)
end
end
test "get_and_update!/3 raises KeyError when the params does not contain the given key" do
assert_raise KeyError, "key :b not found in: %{a: 1}", fn ->
Params.get_and_update!(%{a: 1}, :b, &{&1, 2})
end
assert_raise KeyError, "key :b not found in: %{a: 1}", fn ->
Params.get_and_update!(%{a: 1}, "b", &{&1, 2})
end
assert_raise KeyError, "key \"b\" not found in: %{\"a\" => 1}", fn ->
Params.get_and_update!(%{"a" => 1}, :b, &{&1, 2})
end
assert_raise KeyError, "key \"b\" not found in: %{\"a\" => 1}", fn ->
Params.get_and_update!(%{"a" => 1}, "b", &{&1, 2})
end
end
test "get_and_update!/3 with invalid function" do
message = "the given function must return a two-element tuple or :pop, got: 1"
assert_raise RuntimeError, message, fn ->
Params.get_and_update!(%{a: 1}, :a, fn value -> value end)
end
end
test "get_lazy/3 raises Params.MixedKeysError with params with mixed keys" do
message =
"expected params to be a map with atoms or string keys, " <>
"got a map with mixed keys: %{:b => 2, \"a\" => 1}"
assert_raise Params.MixedKeysError, message, fn ->
Params.get_lazy(%{"a" => 1, b: 2}, :a, fn -> 3 end)
end
end
test "get_lazy/3 raises FunctionClauseError with invalid args" do
assert_raise FunctionClauseError, fn ->
Params.get_lazy(@invalid_params, :a, fn -> 1 end)
end
assert_raise FunctionClauseError, fn ->
Params.get_lazy(%{}, @invalid_key, fn -> 1 end)
end
assert_raise FunctionClauseError, fn ->
invalid_fun = fn value -> value end
Params.get_lazy(%{}, :a, invalid_fun)
end
end
test "has_key?/3 raises Params.MixedKeysError with params with mixed keys" do
message =
"expected params to be a map with atoms or string keys, " <>
"got a map with mixed keys: %{:b => 2, \"a\" => 1}"
assert_raise Params.MixedKeysError, message, fn ->
Params.has_key?(%{"a" => 1, b: 2}, :a)
end
end
test "has_key?/3 raises FunctionClauseError with invalid args" do
assert_raise FunctionClauseError, fn ->
Params.has_key?(@invalid_params, :a)
end
assert_raise FunctionClauseError, fn ->
Params.has_key?(%{}, @invalid_key)
end
end
test "has_keys?/2 raises Params.MixedKeysError with params with mixed keys" do
message =
"expected params to be a map with atoms or string keys, " <>
"got a map with mixed keys: %{:b => 2, \"a\" => 1}"
assert_raise Params.MixedKeysError, message, fn ->
Params.has_keys?(%{"a" => 1, b: 2}, [:a, :b])
end
end
test "has_keys?/2 works with sets" do
assert Params.has_keys?(%{a: 1, b: 2, c: 3}, MapSet.new([:b, "c"]))
assert Params.has_keys?(%{"a" => 1, "b" => 2, "c" => 3}, MapSet.new([:b, "c"]))
end
test "has_keys?/2 works with empty keys" do
assert Params.has_keys?(%{a: 1, b: 2, c: 3}, [])
assert Params.has_keys?(%{"a" => 1, "b" => 2, "c" => 3}, [])
end
test "has_keys?/2 raises FunctionClauseError with invalid args" do
assert_raise FunctionClauseError, fn ->
Params.has_keys?(@invalid_params, [:a])
end
assert_raise FunctionClauseError, fn ->
Params.has_keys?(%{}, @invalid_keys)
end
end
test "merge/2 raises Params.MixedKeysError with params with mixed keys" do
message =
"expected params to be a map with atoms or string keys, " <>
"got a map with mixed keys: %{:b => 2, \"a\" => 1}"
assert_raise Params.MixedKeysError, message, fn ->
Params.merge(%{"a" => 1, b: 2}, %{})
end
assert_raise Params.MixedKeysError, message, fn ->
Params.merge(%{}, %{"a" => 1, b: 2})
end
end
test "merge/2 raises FunctionClauseError with invalid args" do
assert_raise FunctionClauseError, fn ->
Params.merge(@invalid_params, %{})
end
assert_raise FunctionClauseError, fn ->
Params.merge(%{}, @invalid_params)
end
end
test "merge/3 raises Params.MixedKeysError with params with mixed keys" do
message =
"expected params to be a map with atoms or string keys, " <>
"got a map with mixed keys: %{:b => 2, \"a\" => 1}"
assert_raise Params.MixedKeysError, message, fn ->
Params.merge(%{"a" => 1, b: 2}, %{}, fn _k, _v1, _v2 -> 1 end)
end
assert_raise Params.MixedKeysError, message, fn ->
Params.merge(%{}, %{"a" => 1, b: 2}, fn _k, _v1, _v2 -> 1 end)
end
end
test "merge/3 works with params with different lengths" do
# When first map is bigger
assert Params.merge(%{a: 1, b: 2, c: 3}, %{"c" => 4, "d" => 5}, fn :c, 3, 4 -> :x end) ==
%{a: 1, b: 2, c: :x, d: 5}
# When second map is bigger
assert Params.merge(%{"b" => 2, "c" => 3}, %{a: 1, c: 4, d: 5}, fn "c", 3, 4 -> :x end) ==
%{"a" => 1, "b" => 2, "c" => :x, "d" => 5}
end
test "merge/3 raises FunctionClauseError with invalid args" do
assert_raise FunctionClauseError, fn ->
Params.merge(@invalid_params, %{}, fn _k, _v1, _v2 -> 1 end)
end
assert_raise FunctionClauseError, fn ->
Params.merge(%{}, @invalid_params, fn _k, _v1, _v2 -> 1 end)
end
assert_raise FunctionClauseError, fn ->
Params.merge(%{}, %{}, fn -> 1 end)
end
end
test "pop/3 raises Params.MixedKeysError with params with mixed keys" do
message =
"expected params to be a map with atoms or string keys, " <>
"got a map with mixed keys: %{:b => 2, \"a\" => 1}"
assert_raise Params.MixedKeysError, message, fn ->
Params.pop(%{"a" => 1, b: 2}, :a)
end
end
test "pop/3 raises FunctionClauseError with invalid args" do
assert_raise FunctionClauseError, fn ->
Params.pop(@invalid_params, :a)
end
assert_raise FunctionClauseError, fn ->
Params.pop(%{}, @invalid_key)
end
end
test "pop_lazy/3 raises Params.MixedKeysError with params with mixed keys" do
message =
"expected params to be a map with atoms or string keys, " <>
"got a map with mixed keys: %{:b => 2, \"a\" => 1}"
assert_raise Params.MixedKeysError, message, fn ->
Params.pop_lazy(%{"a" => 1, b: 2}, :a, fn -> 1 end)
end
end
test "pop_lazy/3 raises FunctionClauseError with invalid args" do
assert_raise FunctionClauseError, fn ->
Params.pop_lazy(@invalid_params, :a, fn -> 1 end)
end
assert_raise FunctionClauseError, fn ->
Params.pop_lazy(%{}, @invalid_key, fn -> 1 end)
end
assert_raise FunctionClauseError, fn ->
Params.pop_lazy(%{}, :a, fn value -> value end)
end
end
test "put/3 raises Params.MixedKeysError with params with mixed keys" do
message =
"expected params to be a map with atoms or string keys, " <>
"got a map with mixed keys: %{:b => 2, \"a\" => 1}"
assert_raise Params.MixedKeysError, message, fn ->
Params.put(%{"a" => 1, b: 2}, :a, 1)
end
end
test "put/3 raises FunctionClauseError with invalid args" do
assert_raise FunctionClauseError, fn ->
Params.put(@invalid_params, :a, 1)
end
assert_raise FunctionClauseError, fn ->
Params.put(%{}, @invalid_key, 1)
end
end
test "put_new/3 raises Params.MixedKeysError with params with mixed keys" do
message =
"expected params to be a map with atoms or string keys, " <>
"got a map with mixed keys: %{:b => 2, \"a\" => 1}"
assert_raise Params.MixedKeysError, message, fn ->
Params.put_new(%{"a" => 1, b: 2}, :a, 1)
end
end
test "put_new/3 raises FunctionClauseError with invalid args" do
assert_raise FunctionClauseError, fn ->
Params.put_new(@invalid_params, :a, 1)
end
assert_raise FunctionClauseError, fn ->
Params.put_new(%{}, @invalid_key, 1)
end
end
test "put_new_lazy/3 raises Params.MixedKeysError with params with mixed keys" do
message =
"expected params to be a map with atoms or string keys, " <>
"got a map with mixed keys: %{:b => 2, \"a\" => 1}"
assert_raise Params.MixedKeysError, message, fn ->
Params.put_new_lazy(%{"a" => 1, b: 2}, :a, fn -> 1 end)
end
end
test "put_new_lazy/3 raises FunctionClauseError with invalid args" do
assert_raise FunctionClauseError, fn ->
Params.put_new_lazy(@invalid_params, :a, fn -> 1 end)
end
assert_raise FunctionClauseError, fn ->
Params.put_new_lazy(%{}, @invalid_key, fn -> 1 end)
end
assert_raise FunctionClauseError, fn ->
Params.put_new_lazy(%{}, :a, fn value -> value end)
end
end
test "reject/2 raises Params.MixedKeysError with params with mixed keys" do
message =
"expected params to be a map with atoms or string keys, " <>
"got a map with mixed keys: %{:b => 2, \"a\" => 1}"
assert_raise Params.MixedKeysError, message, fn ->
Params.reject(%{"a" => 1, b: 2}, fn _key, value -> is_nil(value) end)
end
end
test "reject/2 raises FunctionClauseError with invalid args" do
assert_raise FunctionClauseError, fn ->
Params.reject(@invalid_params, fn key, value -> value end)
end
assert_raise FunctionClauseError, fn ->
invalid_fun = fn -> {1, 1} end
Params.reject(%{}, invalid_fun)
end
end
test "replace!/3 raises Params.MixedKeysError with params with mixed keys" do
message =
"expected params to be a map with atoms or string keys, " <>
"got a map with mixed keys: %{:b => 2, \"a\" => 1}"
assert_raise Params.MixedKeysError, message, fn ->
Params.replace!(%{"a" => 1, b: 2}, :a, 1)
end
end
test "replace!/3 raises FunctionClauseError with invalid args" do
assert_raise FunctionClauseError, fn ->
Params.replace!(@invalid_params, :a, 1)
end
assert_raise FunctionClauseError, fn ->
Params.replace!(%{}, @invalid_key, 1)
end
end
test "split/3 raises Params.MixedKeysError with params with mixed keys" do
message =
"expected params to be a map with atoms or string keys, " <>
"got a map with mixed keys: %{:b => 2, \"a\" => 1}"
assert_raise Params.MixedKeysError, message, fn ->
Params.split(%{"a" => 1, b: 2}, [:a])
end
end
test "split/2 works with sets" do
assert Params.split(%{a: 1, b: 2, c: 3}, MapSet.new([:b, "c"])) == {%{b: 2, c: 3}, %{a: 1}}
assert Params.split(%{"a" => 1, "b" => 2, "c" => 3}, MapSet.new([:b, "c"])) ==
{%{"b" => 2, "c" => 3}, %{"a" => 1}}
end
test "split/2 works with empty keys" do
assert Params.split(%{a: 1, b: 2, c: 3}, []) == {%{}, %{a: 1, b: 2, c: 3}}
assert Params.split(%{"a" => 1, "b" => 2, "c" => 3}, []) ==
{%{}, %{"a" => 1, "b" => 2, "c" => 3}}
end
test "split/2 raises FunctionClauseError with invalid args" do
assert_raise FunctionClauseError, fn ->
Params.split(@invalid_params, [:a])
end
assert_raise FunctionClauseError, fn ->
Params.split(%{}, @invalid_keys)
end
end
test "take/3 raises Params.MixedKeysError with params with mixed keys" do
message =
"expected params to be a map with atoms or string keys, " <>
"got a map with mixed keys: %{:b => 2, \"a\" => 1}"
assert_raise Params.MixedKeysError, message, fn ->
Params.take(%{"a" => 1, b: 2}, [:a])
end
end
test "take/2 works with sets" do
assert Params.take(%{a: 1, b: 2, c: 3}, MapSet.new([:b, "c"])) == %{b: 2, c: 3}
assert Params.take(%{"a" => 1, "b" => 2, "c" => 3}, MapSet.new([:b, "c"])) ==
%{"b" => 2, "c" => 3}
end
test "take/2 works with empty keys" do
assert Params.take(%{a: 1, b: 2, c: 3}, []) == %{}
assert Params.take(%{"a" => 1, "b" => 2, "c" => 3}, []) == %{}
end
test "take/2 raises FunctionClauseError with invalid args" do
assert_raise FunctionClauseError, fn ->
Params.take(@invalid_params, [:a])
end
assert_raise FunctionClauseError, fn ->
Params.take(%{}, @invalid_keys)
end
end
test "update/4 raises Params.MixedKeysError with params with mixed keys" do
message =
"expected params to be a map with atoms or string keys, " <>
"got a map with mixed keys: %{:b => 2, \"a\" => 1}"
assert_raise Params.MixedKeysError, message, fn ->
Params.update(%{"a" => 1, b: 2}, :a, 1, fn value -> value end)
end
end
test "update/4 raises FunctionClauseError with invalid args" do
assert_raise FunctionClauseError, fn ->
Params.update(@invalid_params, :a, 1, fn value -> value end)
end
assert_raise FunctionClauseError, fn ->
Params.update(%{}, @invalid_key, 1, fn value -> value end)
end
assert_raise FunctionClauseError, fn ->
Params.update(%{}, :a, 1, fn -> 1 end)
end
end
test "update!/3 raises Params.MixedKeysError with params with mixed keys" do
message =
"expected params to be a map with atoms or string keys, " <>
"got a map with mixed keys: %{:b => 2, \"a\" => 1}"
assert_raise Params.MixedKeysError, message, fn ->
Params.update!(%{"a" => 1, b: 2}, :a, fn value -> value end)
end
end
test "update!/3 raises FunctionClauseError with invalid args" do
assert_raise FunctionClauseError, fn ->
Params.update!(@invalid_params, :a, fn value -> value end)
end
assert_raise FunctionClauseError, fn ->
Params.update!(%{}, @invalid_key, fn value -> value end)
end
assert_raise FunctionClauseError, fn ->
Params.update!(%{}, :a, fn -> 1 end)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment