# `Flop.Cursor`
[🔗](https://github.com/woylie/flop/blob/0.26.6/lib/flop/cursor.ex#L1)

Functions for encoding, decoding and extracting cursor values.

## Cursor size

Because cursors arrive as unvalidated request parameters that need to be
decoded before they can be applied, the `decode` functions reject cursors
larger than 8 KB before decoding them. A cursor contains the order
field values of a single record, so the default is far above what Flop itself
produces. If your application produces unusually large cursor values, you can
increase the maximum size either globally via application environment:

    config :flop, max_cursor_size: 16_384

Or by passing the option directly to the `decode` functions:

    Flop.Cursor.decode("g3QAAAABZAACaWRiAAACDg==", max_cursor_size: 16_384)

The option can also be passed to any of the `validate` functions, or to
`use Flop`.

# `decode`
*since 0.8.0* 

```elixir
@spec decode(
  binary(),
  keyword()
) :: {:ok, map()} | :error
```

Decodes a cursor value.

Returns `:error` if the cursor cannot be decoded or the decoded term is not a
map with atom keys.

    iex> Flop.Cursor.decode("g3QAAAABZAACaWRiAAACDg==")
    {:ok, %{id: 526}}

    iex> Flop.Cursor.decode("AAAH")
    :error

    iex> f = fn a -> a + 1 end
    iex> cursor = Flop.Cursor.encode(%{a: f})
    iex> Flop.Cursor.decode(cursor)
    :error

    iex> cursor = Flop.Cursor.encode(a: "b")
    iex> Flop.Cursor.decode(cursor)
    :error

    iex> cursor = Flop.Cursor.encode(%{"a" => "b"})
    iex> Flop.Cursor.decode(cursor)
    :error

Trying to decode a cursor that contains non-existent atoms also results in an
error.

    iex> Flop.Cursor.decode("g3QAAAABZAAGYmFybmV5ZAAGcnViYmVs")
    :error

## Options

- `:max_cursor_size` - The maximum cursor size in bytes that is accepted.
  Defaults to `8192`.

Cursors larger than the configured maximum size as well as compressed terms
are rejected without being decoded. See the module documentation for details.

# `decode!`
*since 0.9.0* 

```elixir
@spec decode!(
  binary(),
  keyword()
) :: map()
```

Same as `Flop.Cursor.decode/1`, but raises an error if the cursor is invalid.

    iex> Flop.Cursor.decode!("g3QAAAABZAACaWRiAAACDg==")
    %{id: 526}

# `encode`
*since 0.8.0* 

```elixir
@spec encode(map()) :: binary()
```

Encodes a cursor value.

    Flop.Cursor.encode(%{email: "peter@mail", name: "Peter"})
    "g3QAAAACdwRuYW1lbQAAAAVQZXRlcncFZW1haWxtAAAACnBldGVyQG1haWw="

# `get_cursor_from_edge`
*since 0.11.0* 

```elixir
@spec get_cursor_from_edge({map(), map()} | map(), [atom()]) :: map()
```

Takes a tuple with the node and the edge and the `order_by` field list and
returns the cursor value derived from the edge map.

If a map is passed instead of a tuple, it retrieves the cursor value from that
map.

This function can be used for the `:cursor_value_func` option. See also
`Flop.Cursor.get_cursor_from_node/2`.

    iex> record = %{id: 20, name: "George", age: 62}
    iex> edge = %{id: 25, relation: "sibling"}
    iex>
    iex> Flop.Cursor.get_cursor_from_edge({record, edge}, [:id])
    %{id: 25}
    iex> Flop.Cursor.get_cursor_from_edge({record, edge}, [:id, :relation])
    %{id: 25, relation: "sibling"}
    iex> Flop.Cursor.get_cursor_from_edge(record, [:id])
    %{id: 20}

If the edge is a struct that derives `Flop.Schema`, join and compound fields
are resolved according to the configuration.

    iex> record = %{id: 25, relation: "sibling"}
    iex> edge = %MyApp.Pet{
    ...>   name: "George",
    ...>   owner: %MyApp.Owner{name: "Carl"}
    ...> }
    iex>
    iex> Flop.Cursor.get_cursor_from_edge({record, edge}, [:owner_name])
    %{owner_name: "Carl"}
    iex> Flop.Cursor.get_cursor_from_edge(edge, [:owner_name])
    %{owner_name: "Carl"}

    iex> record = %{id: 25, relation: "sibling"}
    iex> edge = %MyApp.Pet{
    ...>   given_name: "George",
    ...>   family_name: "Gooney"
    ...> }
    iex> Flop.Cursor.get_cursor_from_edge({record, edge}, [:full_name])
    %{full_name: "Gooney George"}
    iex> Flop.Cursor.get_cursor_from_edge(edge, [:full_name])
    %{full_name: "Gooney George"}

# `get_cursor_from_node`
*since 0.11.0* 

```elixir
@spec get_cursor_from_node({map(), map()} | map(), [atom()]) :: map()
```

Takes a tuple with the node and the edge and the `order_by` field list and
returns the cursor value derived from the node map.

If a map is passed instead of a tuple, it retrieves the cursor value from that
map.

This function is used as a default if no `:cursor_value_func` option is
set. See also `Flop.Cursor.get_cursor_from_edge/2`.

    iex> record = %{id: 20, name: "George", age: 62}
    iex> edge = %{id: 25, relation: "sibling"}
    iex>
    iex> Flop.Cursor.get_cursor_from_node({record, edge}, [:id])
    %{id: 20}
    iex> Flop.Cursor.get_cursor_from_node({record, edge}, [:id, :name])
    %{id: 20, name: "George"}
    iex> Flop.Cursor.get_cursor_from_node(record, [:id])
    %{id: 20}

If the node is a struct that derives `Flop.Schema`, join and compound fields
are resolved according to the configuration.

    iex> record = %MyApp.Pet{
    ...>   name: "George",
    ...>   owner: %MyApp.Owner{name: "Carl"}
    ...> }
    iex> edge = %{id: 25, relation: "sibling"}
    iex>
    iex> Flop.Cursor.get_cursor_from_node({record, edge}, [:owner_name])
    %{owner_name: "Carl"}
    iex> Flop.Cursor.get_cursor_from_node(record, [:owner_name])
    %{owner_name: "Carl"}

    iex> record = %MyApp.Pet{
    ...>   given_name: "George",
    ...>   family_name: "Gooney"
    ...> }
    iex> edge = %{id: 25, relation: "sibling"}
    iex> Flop.Cursor.get_cursor_from_node({record, edge}, [:full_name])
    %{full_name: "Gooney George"}
    iex> Flop.Cursor.get_cursor_from_node(record, [:full_name])
    %{full_name: "Gooney George"}

# `get_cursors`
*since 0.8.0* 

```elixir
@spec get_cursors([any()], [atom()], [Flop.option()]) ::
  {binary(), binary()} | {nil, nil}
```

Retrieves the start and end cursors from a query result.

    iex> results = [%{name: "Mary"}, %{name: "Paul"}, %{name: "Peter"}]
    iex> order_by = [:name]
    iex>
    iex> {start_cursor, end_cursor} =
    ...>   Flop.Cursor.get_cursors(results, order_by)
    {"g3QAAAABdwRuYW1lbQAAAARNYXJ5", "g3QAAAABdwRuYW1lbQAAAAVQZXRlcg=="}
    iex>
    iex> Flop.Cursor.decode(start_cursor)
    {:ok, %{name: "Mary"}}
    iex> Flop.Cursor.decode(end_cursor)
    {:ok, %{name: "Peter"}}

If the result set is empty, the cursor values will be `nil`.

    iex> Flop.Cursor.get_cursors([], [:id])
    {nil, nil}

The default function to retrieve the cursor value from the query result is
`Flop.Cursor.get_cursor_from_node/2`, which expects the query result to be a
map or a 2-tuple. You can set the `cursor_value_func` option to use
another function. Flop also comes with `Flop.Cursor.get_cursor_from_edge/2`.

If the records in the result set are not maps, you can define a custom cursor
value function like this:

    iex> results = [{"Mary", 1936}, {"Paul", 1937}, {"Peter", 1938}]
    iex> cursor_func = fn {name, year}, order_fields ->
    ...>   Enum.into(order_fields, %{}, fn
    ...>     :name -> {:name, name}
    ...>     :year -> {:year, year}
    ...>   end)
    ...> end
    iex> opts = [cursor_value_func: cursor_func]
    iex>
    iex> {start_cursor, end_cursor} =
    ...>   Flop.Cursor.get_cursors(results, [:name, :year], opts)
    {"g3QAAAACdwRuYW1lbQAAAARNYXJ5dwR5ZWFyYgAAB5A=",
      "g3QAAAACdwRuYW1lbQAAAAVQZXRlcncEeWVhcmIAAAeS"}
    iex>
    iex> Flop.Cursor.decode(start_cursor)
    {:ok, %{name: "Mary", year: 1936}}
    iex> Flop.Cursor.decode(end_cursor)
    {:ok, %{name: "Peter", year: 1938}}

---

*Consult [api-reference.md](api-reference.md) for complete listing*
