lib/live_query/query/def.ex

defmodule LiveQuery.Query.Def do
  @moduledoc """
  This module defines the `LiveQuery.Query.Def` behaviour and struct.
  The behaviour can be used to make a module-base query definition.
  The struct can be used as a query definition itself.
  """

  @enforce_keys [:init]
  defstruct [:init, :handle_call, :handle_cast, :handle_info]

  @type data :: any
  @type t :: %__MODULE__{
          init: (state :: %{required(:key) => any, required(:config) => map} -> data),
          handle_call:
            (msg :: any,
             from :: GenServer.from(),
             state :: %{
               required(:key) => any,
               required(:config) => map,
               optional(:data) => data
             } ->
               {:noreply, data} | {:reply, any, data})
            | nil,
          handle_cast:
            (msg :: any,
             state :: %{
               required(:key) => any,
               required(:config) => map,
               optional(:data) => data
             } ->
               data)
            | nil,
          handle_info:
            (msg :: any,
             state :: %{
               required(:key) => any,
               required(:config) => map,
               optional(:data) => data
             } ->
               data)
            | nil
        }

  @callback init(state :: %{required(:key) => any, required(:config) => map}) :: data
  @callback handle_call(
              msg :: any,
              from :: GenServer.from(),
              state :: %{required(:key) => any, required(:config) => map, optional(:data) => data}
            ) :: {:noreply, data} | {:reply, any, data}
  @callback handle_cast(
              msg :: any,
              state :: %{required(:key) => any, required(:config) => map, optional(:data) => data}
            ) :: data
  @callback handle_info(
              msg :: any,
              state :: %{required(:key) => any, required(:config) => map, optional(:data) => data}
            ) :: data

  @optional_callbacks [handle_call: 3, handle_cast: 2, handle_info: 2]

  @doc """
  Just adds the `LiveQuery.Query.Def` behaviour to your module for you.
  Modules which implement this behaviour can be used as a query definition.
  """
  defmacro __using__(_opts) do
    quote do
      @behaviour LiveQuery.Query.Def
    end
  end

  @doc """
  Creates a new `LiveQuery.Query.Def` struct which implements the `LiveQuery.Query.DefLike` protocol.
  """
  @spec new(%{
          required(:init) =>
            (state :: %{required(:key) => any, required(:config) => map} ->
               data),
          optional(:handle_call) =>
            (msg :: any,
             from :: GenServer.from(),
             state :: %{
               required(:key) => any,
               required(:config) => map,
               optional(:data) => data
             } ->
               {:noreply, data} | {:reply, any, data}),
          optional(:handle_cast) =>
            (msg :: any,
             state :: %{
               required(:key) => any,
               required(:config) => map,
               optional(:data) => data
             } ->
               data),
          optional(:handle_info) =>
            (msg :: any,
             state :: %{
               required(:key) => any,
               required(:config) => map,
               optional(:data) => data
             } ->
               data)
        }) :: t
  def new(opts) do
    %__MODULE__{
      init: opts.init,
      handle_call: opts[:handle_call],
      handle_cast: opts[:handle_cast],
      handle_info: opts[:handle_info]
    }
  end

  defimpl LiveQuery.Query.DefLike do
    def init(self, state) do
      self.init.(state)
    end

    def handle_call(self, msg, from, state) do
      self.handle_call.(msg, from, state)
    end

    def handle_cast(self, msg, state) do
      self.handle_cast.(msg, state)
    end

    def handle_info(self, msg, state) do
      self.handle_info.(msg, state)
    end
  end
end