Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 20 additions & 5 deletions lib/paginator.ex
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ defmodule Paginator do

import Ecto.Query

alias Paginator.{Config, Cursor, Ecto.Query, Page, Page.Metadata}
alias Paginator.{Config, Ecto.Query, Page, Page.Metadata}

defmacro __using__(opts) do
quote do
Expand All @@ -42,8 +42,19 @@ defmodule Paginator do
opts = Keyword.merge(@defaults, opts)
config = Config.new(opts)

unless config.cursor_fields,
do: raise("expected `:cursor_fields` to be set in call to paginate/3")
case config do
%{cursor_fields: nil} ->
raise("expected `:cursor_fields` to be set in call to paginate/3")

%{after_values: {:error, err}} ->
raise("error decoding `:after` cursor (#{err})")

%{before_values: {:error, err}} ->
raise("error decoding `:before` cursor (#{err})")

_ ->
nil
end

Paginator.paginate(queryable, config, __MODULE__, repo_opts)
end
Expand Down Expand Up @@ -153,10 +164,14 @@ defmodule Paginator do
end
end

defp fetch_cursor_value(schema, %Config{cursor_fields: cursor_fields}) do
defp fetch_cursor_value(schema, %Config{
cursor_fields: cursor_fields,
cursor_module: cursor_module,
cursor_module_opts: cursor_module_opts
}) do
cursor_fields
|> Enum.map(fn field -> Map.get(schema, field) end)
|> Cursor.encode()
|> cursor_module.encode!(cursor_module_opts)
end

defp first_page?(sorted_entries, %Config{limit: limit}) do
Expand Down
15 changes: 11 additions & 4 deletions lib/paginator/config.ex
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
defmodule Paginator.Config do
@moduledoc false

alias Paginator.Cursor

@type t :: %__MODULE__{}

defstruct [
Expand All @@ -11,6 +9,8 @@ defmodule Paginator.Config do
:before,
:before_values,
:cursor_fields,
:cursor_module,
:cursor_module_opts,
:include_total_count,
:limit,
:maximum_limit,
Expand All @@ -22,21 +22,28 @@ defmodule Paginator.Config do
@minimum_limit 1
@maximum_limit 500
@default_total_count_limit 10_000
@default_cursor_module Paginator.Cursors.UnencryptedCursor

def new(opts \\ []) do
%__MODULE__{
after: opts[:after],
after_values: Cursor.decode(opts[:after]),
after_values: cursor_module(opts).decode(opts[:after], opts[:cursor_module_opts]),
before: opts[:before],
before_values: Cursor.decode(opts[:before]),
before_values: cursor_module(opts).decode(opts[:before], opts[:cursor_module_opts]),
cursor_fields: opts[:cursor_fields],
cursor_module: cursor_module(opts),
cursor_module_opts: opts[:cursor_module_opts] || [],
include_total_count: opts[:include_total_count] || false,
limit: limit(opts),
sort_direction: opts[:sort_direction] || :asc,
total_count_limit: opts[:total_count_limit] || @default_total_count_limit
}
end

defp cursor_module(opts) do
opts[:cursor_module] || @default_cursor_module
end

defp limit(opts) do
max(opts[:limit] || @default_limit, @minimum_limit)
|> min(opts[:maximum_limit] || @maximum_limit)
Expand Down
24 changes: 5 additions & 19 deletions lib/paginator/cursor.ex
Original file line number Diff line number Diff line change
@@ -1,21 +1,7 @@
defmodule Paginator.Cursor do
@moduledoc false

def decode(nil), do: nil

def decode(encoded_cursor) do
encoded_cursor
|> Base.url_decode64!()
|> :erlang.binary_to_term()
end

def encode(values) when is_list(values) do
values
|> :erlang.term_to_binary()
|> Base.url_encode64()
end

def encode(value) do
encode([value])
end
@callback decode(String.t(), opts :: list()) :: {:ok, term} | {:error, term}
@callback decode!(String.t(), opts :: list()) :: term
@callback encode(cursor_fields :: term, opts :: list()) ::
{:ok, encoded :: String.t()} | {:error, term}
@callback encode!(cursor_fields :: term, opts :: list()) :: encoded :: String.t()
end
39 changes: 39 additions & 0 deletions lib/paginator/cursors/unencrypted_cursor.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
defmodule Paginator.Cursors.UnencryptedCursor do
@behaviour Paginator.Cursor
@moduledoc false

def decode(cursor, opts \\ [])
def decode(nil, _opts), do: nil

def decode(encoded_cursor, _opts) do
{:ok,
encoded_cursor
|> Base.url_decode64!()
|> :erlang.binary_to_term()}
end

def decode!(encoded_cursor, opts \\ []) do
with {:ok, decoded} <- decode(encoded_cursor, opts) do
decoded
end
end

def encode(values, opts \\ [])

def encode(values, _opts) when is_list(values) do
{:ok,
values
|> :erlang.term_to_binary()
|> Base.url_encode64()}
end

def encode(value, opts) do
encode([value], opts)
end

def encode!(value, opts \\ []) do
with {:ok, encoded} <- encode(value, opts) do
encoded
end
end
end
16 changes: 8 additions & 8 deletions lib/paginator/ecto/query.ex
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ defmodule Paginator.Ecto.Query do
end

defp maybe_where(query, %Config{
after_values: after_values,
after_values: {:ok, after_values},
before: nil,
cursor_fields: cursor_fields,
sort_direction: :asc
Expand All @@ -72,7 +72,7 @@ defmodule Paginator.Ecto.Query do

defp maybe_where(query, %Config{
after_values: nil,
before_values: before_values,
before_values: {:ok, before_values},
cursor_fields: cursor_fields,
sort_direction: :asc
}) do
Expand All @@ -82,8 +82,8 @@ defmodule Paginator.Ecto.Query do
end

defp maybe_where(query, %Config{
after_values: after_values,
before_values: before_values,
after_values: {:ok, after_values},
before_values: {:ok, before_values},
cursor_fields: cursor_fields,
sort_direction: :asc
}) do
Expand All @@ -101,7 +101,7 @@ defmodule Paginator.Ecto.Query do
end

defp maybe_where(query, %Config{
after_values: after_values,
after_values: {:ok, after_values},
before: nil,
cursor_fields: cursor_fields,
sort_direction: :desc
Expand All @@ -112,7 +112,7 @@ defmodule Paginator.Ecto.Query do

defp maybe_where(query, %Config{
after: nil,
before_values: before_values,
before_values: {:ok, before_values},
cursor_fields: cursor_fields,
sort_direction: :desc
}) do
Expand All @@ -122,8 +122,8 @@ defmodule Paginator.Ecto.Query do
end

defp maybe_where(query, %Config{
after_values: after_values,
before_values: before_values,
after_values: {:ok, after_values},
before_values: {:ok, before_values},
cursor_fields: cursor_fields,
sort_direction: :desc
}) do
Expand Down
3 changes: 2 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ defmodule Paginator.Mixfile do
{:ex_doc, "~> 0.18", only: :dev, runtime: false},
{:ex_machina, "~> 2.1", only: :test},
{:inch_ex, "~> 0.5", only: [:dev, :test]},
{:postgrex, "~> 0.13", optional: true}
{:postgrex, "~> 0.13", optional: true},
{:plug, "~> 1.4", optional: true}
]
end

Expand Down
2 changes: 2 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
"idna": {:hex, :idna, "5.1.0", "d72b4effeb324ad5da3cab1767cb16b17939004e789d8c0ad5b70f3cea20c89a", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
"inch_ex": {:hex, :inch_ex, "0.5.6", "418357418a553baa6d04eccd1b44171936817db61f4c0840112b420b8e378e67", [:mix], [{:poison, "~> 1.5 or ~> 2.0 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
"mime": {:hex, :mime, "1.2.0", "78adaa84832b3680de06f88f0997e3ead3b451a440d183d688085be2d709b534", [:mix], [], "hexpm"},
"mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"},
"plug": {:hex, :plug, "1.4.5", "7b13869283fff6b8b21b84b8735326cc012c5eef8607095dc6ee24bd0a273d8e", [], [{:cowboy, "~> 1.0.1 or ~> 1.1", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"},
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
"poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], [], "hexpm"},
"postgrex": {:hex, :postgrex, "0.13.4", "f58e319c5451bfda86ba6a45ce6dca311193d0a9861323d0d16e8d02e25adc41", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm"},
Expand Down
27 changes: 18 additions & 9 deletions test/paginator/config_test.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
defmodule Paginator.ConfigTest do
use ExUnit.Case, async: true

alias Paginator.{Config, Cursor}
alias Paginator.Config
alias Paginator.Cursors.UnencryptedCursor, as: Cursor
alias Paginator.Cursors.EncryptedCursor

describe "Config.new/2" do
test "creates a new config" do
Expand All @@ -12,6 +14,13 @@ defmodule Paginator.ConfigTest do
assert config.limit == 10
assert config.cursor_fields == [:id]
end

test "creates a new config with custom cursor_module" do
config = Config.new(cursor_fields: [:id], cursor_module: EncryptedCursor, cursor_module_opts: [encryption_key: "123", signing_key: "321"])

assert config.cursor_module == EncryptedCursor
assert config.cursor_module_opts == [encryption_key: "123", signing_key: "321"]
end
end

describe "Config.new/2 applies min/max limit" do
Expand Down Expand Up @@ -44,15 +53,15 @@ defmodule Paginator.ConfigTest do
config = Config.new(limit: 10, cursor_fields: [:id], before: simple_before())

assert config.after_values == nil
assert config.before_values == ["pay_789"]
assert config.before_values == {:ok, ["pay_789"]}
assert config.limit == 10
assert config.cursor_fields == [:id]
end

test "simple after" do
config = Config.new(limit: 10, cursor_fields: [:id], after: simple_after())

assert config.after_values == ["pay_123"]
assert config.after_values == {:ok, ["pay_123"]}
assert config.before_values == nil
assert config.limit == 10
assert config.cursor_fields == [:id]
Expand All @@ -62,23 +71,23 @@ defmodule Paginator.ConfigTest do
config = Config.new(limit: 10, cursor_fields: [:created_at, :id], before: complex_before())

assert config.after_values == nil
assert config.before_values == ["2036-02-09T20:00:00.000Z", "pay_789"]
assert config.before_values == {:ok, ["2036-02-09T20:00:00.000Z", "pay_789"]}
assert config.limit == 10
assert config.cursor_fields == [:created_at, :id]
end

test "complex after" do
config = Config.new(limit: 10, cursor_fields: [:created_at, :id], after: complex_after())

assert config.after_values == ["2036-02-09T20:00:00.000Z", "pay_123"]
assert config.after_values == {:ok, ["2036-02-09T20:00:00.000Z", "pay_123"]}
assert config.before_values == nil
assert config.limit == 10
assert config.cursor_fields == [:created_at, :id]
end
end

def simple_after, do: Cursor.encode("pay_123")
def simple_before, do: Cursor.encode("pay_789")
def complex_after, do: Cursor.encode(["2036-02-09T20:00:00.000Z", "pay_123"])
def complex_before, do: Cursor.encode(["2036-02-09T20:00:00.000Z", "pay_789"])
def simple_after, do: Cursor.encode!("pay_123")
def simple_before, do: Cursor.encode!("pay_789")
def complex_after, do: Cursor.encode!(["2036-02-09T20:00:00.000Z", "pay_123"])
def complex_before, do: Cursor.encode!(["2036-02-09T20:00:00.000Z", "pay_789"])
end
Loading