lib/quarto/cursor/base64.ex

defmodule Quarto.Cursor.Base64 do
  @moduledoc """
  Default cursor encoder/decoder

  Will encode any erlang term to base 64 and vice-versa.

  See also `Quarto.Cursor.Encode` for the protocol used to encode/decode individual
  values.
  """
  alias Quarto.Cursor
  @behaviour Quarto.Cursor

  @impl true
  def decode(cursor, opts \\ [])
  def decode(nil, _opts), do: {:ok, nil}

  def decode(encoded_cursor, _opts) do
    case encoded_cursor |> Base.url_decode64() do
      {:ok, decoded} ->
        {:ok,
         decoded
         |> Plug.Crypto.non_executable_binary_to_term([:safe])
         |> Enum.map(&Cursor.Decode.convert/1)}

      :error ->
        {:error, :base64_decode_error}
    end
  end

  @impl true
  def decode!(encoded_cursor, opts \\ []) do
    case decode(encoded_cursor, opts) do
      {:ok, cursor} -> cursor
      {:error, error} -> raise(ArgumentError, "error decoding cursor (#{error})")
    end
  end

  @impl true
  def encode(values, opts \\ [])

  def encode(values, _opts) when is_list(values) do
    result =
      values
      |> Enum.map(&Cursor.Encode.convert/1)
      |> :erlang.term_to_binary()
      |> Base.url_encode64()

    {:ok, result}
  end

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

  @impl true
  def encode!(values, opts \\ []) do
    {:ok, cursor} = encode(values, opts)
    cursor
  end
end