defmodule Forage.Codec.Encoder do
@moduledoc """
Functionality to encode a `Forage.Plan` into a Phoenix `param` map
for use with the `ApplicationWeb.Router.Helpers`.
"""
alias Forage.ForagePlan
alias Forage.ForagePlan.{
Sort,
Pagination,
Filter
}
@doc """
Adds extra filters to a `params` map in the form
of a list of pairs (either a list of the form `[{"key", value}]` or
a keyword list `[key: value]`).
This function trades explicitness for succintness.
It only suppotrs `equal_to` filters.
"""
def with_extra_filters(params, []), do: params
def with_extra_filters(params, new_filters) do
filters = Map.get(params, "_filter", %{})
new_filters =
Enum.reduce(new_filters, filters, fn {field, value}, current_filters ->
stringified_field = to_string(field)
updated_filters =
Map.put(
current_filters,
stringified_field,
%{"op" => "equal_to", "val" => value}
)
updated_filters
end)
Map.put(params, "_filter", new_filters)
end
@doc """
Encodes a forage plan into a params map.
This function doesn't need to take the schema as an argument
because it will never have to convert a string into an atom
(the params map contains only strings and never atoms)
"""
def encode(%ForagePlan{} = plan) do
# Each of the forage components (filter, sort and pagination) will be encoded as maps,
# so that they can simply be merged together
filter_map = encode_filter(plan)
sort_map = encode_sort(plan)
pagination_map = encode_pagination(plan)
# Merge the three maps
filter_map |> Map.merge(sort_map) |> Map.merge(pagination_map)
end
@doc """
Encode the "filter" part of a forage plan. Returns a map.
"""
def encode_filter(%ForagePlan{filter: %Filter{field: nil, operator: nil, value: nil}} = _plan) do
%{}
end
def encode_filter(%ForagePlan{filter: filter} = _plan) do
filter_value =
for filter_filter <- filter, into: %{} do
field_name =
case filter_filter.field do
{:simple, name} when is_atom(name) ->
Atom.to_string(name)
{:assoc, {_schema, local, remote}} when is_atom(local) and is_atom(remote) ->
local_string = Atom.to_string(local)
remote_string = Atom.to_string(remote)
local_string <> "." <> remote_string
end
# Return key-value pair
{field_name,
%{
"op" => filter_filter.operator,
"val" => filter_filter.value
}}
end
%{"_filter" => filter_value}
end
@doc """
Encode the "sort" part of a forage plan. Returns a map.
"""
def encode_sort(%ForagePlan{sort: %Sort{field: nil, direction: nil}} = _plan) do
%{}
end
def encode_sort(%ForagePlan{sort: sort} = _plan) do
sort_value =
for sort_column <- sort, into: %{} do
field_name = Atom.to_string(sort_column.field)
direction_name = Atom.to_string(sort_column.direction)
# Return key-value pair
{field_name, %{"direction" => direction_name}}
end
%{"_sort" => sort_value}
end
@doc """
Encode the "pagination" part of a forage plan. Returns a map.
"""
def encode_pagination(%ForagePlan{pagination: %Pagination{} = pagination} = _plan) do
encoded_after =
case pagination.after do
nil-> %{}
value -> %{"after" => value}
end
encoded_before =
case pagination.before do
nil -> %{}
value -> %{"before" => value}
end
Map.merge(encoded_after, encoded_before)
end
end