lib/elven_gard/network/view.ex

defmodule ElvenGard.Network.View do
  @moduledoc ~S"""
  Define a custom behaviour for a View (packet sent from server to client).

  This module defines a behaviour that allows users to create custom packet views
  to be sent from the server to the client. By implementing the callback defined
  in this module, users can define how a specific packet view is rendered and
  converted into binary data for transmission.

  To implement a custom view, you need to define the `c:render/2` callback. The
  `c:render/2` function takes the name of the packet view and a map of parameters
  as input and returns the binary data to be sent to the client or a structure 
  that will be serialized by `ElvenGard.Network.PacketSerializer`.

  ## Example

  Here's an example of defining a custom view for rendering a login response
  packet:

      defmodule MyApp.LoginViews do
        use ElvenGard.Network.View
        
        alias MyApp.Server.LoginPackets.LoginResponse

        @impl ElvenGard.Network.View
        def render(:login_response, %{status: status, message: message}) do
          # Encode the `json` field before generating the LoginResponse struct
          json = Poison.encode!(message)
          %LoginResponse{status: status, json: json}
        end
      end

  In the above example, we defined a custom views module `MyApp.LoginViews` that
  implements the `c:render/2` callback for rendering a login response packet.
  """

  alias ElvenGard.Network.UnknownViewError

  @doc """
  Build a packet to send to the client.

  Arguments:

  - `name`: A unique identifier packet view (string or atom).
  - `params`: A map or keyword list of parameters to be used when rendering the view.

  The function should return an iodata or a structure to be sent to the client.
  """
  @callback render(name, params) :: iodata() | struct()
            when name: atom() | String.t(), params: map() | Keyword.t()

  @doc false
  defmacro __using__(_) do
    quote do
      @behaviour unquote(__MODULE__)
      @before_compile unquote(__MODULE__)
    end
  end

  @doc false
  defmacro __before_compile__(_env) do
    # We are using `generated: true` because we don't want warnings coming
    # from `c:render/2` to be reported in case the user has defined a
    # catch-all `c:render/2` clause.
    quote generated: true do
      def render(type, _args) do
        raise UnknownViewError, parent: unquote(__CALLER__.module), type: type
      end
    end
  end
end