Current section

Files

Jump to
ecto_interface lib ecto_interface paginator config.ex
Raw

lib/ecto_interface/paginator/config.ex

defmodule EctoInterface.Paginator.Config do
@moduledoc """
The configuration record of a pagination request.
"""
@type t :: %__MODULE__{}
defstruct [
:after,
:after_values,
:before,
:before_values,
:cursor_fields,
:fetch_cursor_value_fun,
:include_total_count,
:total_count_primary_key_field,
:limit,
:maximum_limit,
:sort_direction,
:total_count_limit
]
defmodule ArgumentError do
defexception [:message]
end
@default_total_count_primary_key_field :id
@default_limit 50
@minimum_limit 1
@maximum_limit 500
@default_total_count_limit 10_000
@order_directions [
:asc,
:asc_nulls_last,
:asc_nulls_first,
:desc,
:desc_nulls_first,
:desc_nulls_last
]
def new(opts \\ []) do
%__MODULE__{
after: opts[:after],
after_values: EctoInterface.Paginator.Cursor.decode(opts[:after]),
before: opts[:before],
before_values: EctoInterface.Paginator.Cursor.decode(opts[:before]),
cursor_fields: opts[:cursor_fields],
fetch_cursor_value_fun:
opts[:fetch_cursor_value_fun] || (&EctoInterface.Paginator.default_fetch_cursor_value/2),
include_total_count: opts[:include_total_count] || false,
total_count_primary_key_field:
opts[:total_count_primary_key_field] || @default_total_count_primary_key_field,
limit: limit(opts),
sort_direction: opts[:sort_direction],
total_count_limit: opts[:total_count_limit] || @default_total_count_limit
}
|> convert_deprecated_config()
end
def validate!(%__MODULE__{} = config) do
unless config.cursor_fields do
raise(EctoInterface.Paginator.Config.ArgumentError,
message: "expected `:cursor_fields` to be set"
)
end
if !cursor_values_match_cursor_fields?(config.after_values, config.cursor_fields) do
raise(EctoInterface.Paginator.Config.ArgumentError,
message: "expected `:after` cursor to match `:cursor_fields`"
)
end
if !cursor_values_match_cursor_fields?(config.before_values, config.cursor_fields) do
raise(EctoInterface.Paginator.Config.ArgumentError,
message: "expected `:before` cursor to match `:cursor_fields`"
)
end
end
defp cursor_values_match_cursor_fields?(nil = _cursor_values, _cursor_fields), do: true
defp cursor_values_match_cursor_fields?(cursor_values, _cursor_fields)
when is_list(cursor_values) do
# Legacy cursors are valid by default
true
end
defp cursor_values_match_cursor_fields?(cursor_values, cursor_fields) do
cursor_keys = cursor_values |> Map.keys() |> Enum.sort()
sorted_cursor_fields =
cursor_fields
|> Enum.map(fn
{field, value} when is_atom(field) and value in @order_directions ->
field
{{schema, field}, value}
when is_atom(schema) and is_atom(field) and value in @order_directions ->
{schema, field}
{{field, func}, value}
when is_function(func) and is_atom(field) and value in @order_directions ->
field
{field, func} when is_function(func) and is_atom(field) ->
field
field when is_atom(field) ->
field
{schema, field} when is_atom(schema) and is_atom(field) ->
{schema, field}
end)
|> Enum.sort()
match?(^cursor_keys, sorted_cursor_fields)
end
defp limit(opts) do
max(opts[:limit] || @default_limit, @minimum_limit)
|> min(opts[:maximum_limit] || @maximum_limit)
end
defp convert_deprecated_config(config) do
case config do
%__MODULE__{sort_direction: nil} ->
%{
config
| cursor_fields: build_cursor_fields_from_sort_direction(config.cursor_fields, :asc)
}
%__MODULE__{sort_direction: direction} ->
%{
config
| cursor_fields:
build_cursor_fields_from_sort_direction(config.cursor_fields, direction),
sort_direction: nil
}
end
end
defp build_cursor_fields_from_sort_direction(nil, _sorting_direction), do: nil
defp build_cursor_fields_from_sort_direction(fields, sorting_direction) do
Enum.map(fields, fn
{{_binding, _column}, _direction} = field -> field
{_column, direction} = field when direction in @order_directions -> field
{_binding, _column} = field -> {field, sorting_direction}
field -> {field, sorting_direction}
end)
end
end