Packages

Set of utility functions initially written for the Malan project, but now independent. Provides Phoenix-related utility functions.

Current section

Files

Jump to
malan_phoenix_utils lib malan_phoenix_utils.ex
Raw

lib/malan_phoenix_utils.ex

defmodule MalanPhoenixUtils.IPv4 do
def to_s(%Plug.Conn{} = conn), do: to_s(conn.remote_ip)
def to_s(ip_tuple), do: MalanUtils.IPv4.to_s(ip_tuple)
end
defmodule MalanPhoenixUtils.Phoenix.Controller do
import Plug.Conn, only: [halt: 1, put_status: 2]
require Logger
def halt_status(conn, status) do
Logger.debug("[halt_status]: status: #{status}")
conn
|> put_status(status)
|> Phoenix.Controller.put_view(MalanWeb.ErrorView)
|> Phoenix.Controller.render("#{status}.json")
|> halt()
end
def remote_ip_s(conn), do: MalanUtils.IPv4.to_s(conn.remote_ip)
end
defmodule MalanPhoenixUtils.Ecto do
@doc ~S"""
If any of the top-level properties are `Ecto.Association.NotLoaded`, remove them.
Note that if `map` is actually a `struct` this won't work. You should first convert
it to a map:
```
Map.from_struct(struct)
```
"""
def remove_not_loaded(map) do
Enum.filter(map, fn
{_k, %Ecto.Association.NotLoaded{} = _v} -> false
{_k, _v} -> true
end)
|> Enum.into(%{})
end
end
defmodule MalanPhoenixUtils.Ecto.Query do
defguard valid_sort(sort) when is_atom(sort) and sort in [:asc, :desc]
end
defmodule MalanPhoenixUtils.Ecto.Changeset do
@doc """
Validates that the property specified does NOT match the provided regex.
This function is essentially the opposite of validate_format()
"""
def validate_not_format(nil, _regex), do: false
def validate_not_format(value, regex), do: value =~ regex
def validate_not_format(changeset, property, regex) do
case validate_not_format(Map.get(changeset.changes, property), regex) do
true -> Ecto.Changeset.add_error(changeset, property, "has invalid format")
false -> changeset
end
end
def validate_ip_addr(changeset, property, allow_empty? \\ false) do
val = Ecto.Changeset.get_change(changeset, property)
cond do
allow_empty? && val == "" ->
changeset
Iptools.is_ipv4?(val) ->
changeset
true ->
Ecto.Changeset.add_error(
changeset,
property,
"#{property} must be a valid IPv4 or IPv6 address"
)
end
end
@doc ~S"""
Convert changeset errors into a list of `String`s
## Examples
MalanPhoenixUtils.Ecto.Changeset.errors_to_str_list(changeset)
# TODO example needs updated
[who: {"who must be a valid ID of a user", []}]
"""
def errors_to_str_list(%Ecto.Changeset{errors: errors}),
do: errors_to_str_list(errors)
def errors_to_str_list(errors) do
Enum.map(errors, fn
{field, {err_msg, _attrs}} -> "#{field}: #{err_msg}"
end)
end
@doc ~S"""
Convert changeset errors into a `String`
## Examples
MalanPhoenixUtils.Ecto.Changeset.errors_to_str_list(changeset)
# TODO example needs updated
[who: {"who must be a valid ID of a user", []}]
"""
def errors_to_str(%Ecto.Changeset{} = changeset) do
errors_to_str_list(changeset)
|> Enum.join(", ")
end
def errors_to_str(:too_many_requests) do
"Rate limit exceeded"
end
@doc ~S"""
If any of the top-level keys in `data` are `Ecto.Changeset`s, apply their changes.
This is not recursive. It onliy does the top level. Also changes are applied
whether they are valid or not, so consider whether that's the behavior you want.
"""
def convert_changes(%Ecto.Changeset{changes: changes}), do: convert_changes(changes)
def convert_changes(%{__struct__: struct_type} = data) do
data
|> Map.from_struct()
|> convert_changes(struct_type)
end
def convert_changes(%{} = data) do
data
|> Enum.map(fn
{k, %Ecto.Changeset{} = v} ->
{k, Ecto.Changeset.apply_changes(v)}
{k, va} when is_list(va) ->
{k,
Enum.map(va, fn
%Ecto.Changeset{} = v -> Ecto.Changeset.apply_changes(v)
v -> v
end)}
{k, v} ->
{k, v}
end)
|> Enum.into(%{})
end
def convert_changes(data), do: data
def convert_changes(data, struct_type) do
struct(struct_type, convert_changes(data))
end
end