lib/exampple/xmpp/envelope.ex

defmodule Exampple.Xmpp.Envelope do
  @moduledoc """
  Envelope helps to create a function to be stored inside of the
  `Exampple.Router.Conn` which will be in use when a response will
  be processed.

  The idea of this module was based on handling the
  [Namespace delegation (XEP-0355)](https://xmpp.org/extensions/xep-0355.html)
  but also the [Stanza forwarding (XEP-0297)](https://xmpp.org/extensions/xep-0297.html).

  This way we process the stanzas wrapped using the `Exampple.Router`
  and the configuration provided by our router configuration in a
  transparent way.
  """
  alias Exampple.Router.Conn
  alias Exampple.Xml.Xmlel
  alias Exampple.Xmpp.Stanza

  @doc """
  Handle the stanza inside of a `Exampple.Router.Conn` provided by
  the first parameter `conn` and creates an envelope function with it.
  The function is placed inside of a new `Exampple.Router.Conn` parsed
  with the data provided from the internal stanza. The `query` provided
  as second parameter let us detect where is the actual stanza to be
  propagated and where we have to inject the response.
  """
  def handle(%Conn{stanza_type: "message"}, _query) do
    nil
  end

  def handle(%Conn{stanza_type: "iq" = stanza_type} = conn, query) do
    base = get_when(query, stanza_type)

    envelope = fn internal_stanza ->
      payload = insert_when(query, stanza_type, internal_stanza)
      Stanza.iq_resp(conn, [payload])
    end

    conn =
      Conn.new(base, conn.domain)
      |> Map.put(:envelope, envelope)

    %Xmlel{children: query} = base
    {conn, query}
  end

  defp get_when([%Xmlel{name: name} = xmlel | _], name), do: xmlel

  defp get_when([%Xmlel{children: children} | _], name) do
    get_when(children, name)
  end

  defp insert_when([%Xmlel{name: name} | _], name, payload), do: payload

  defp insert_when([%Xmlel{children: children} = xmlel | _], name, payload) do
    %Xmlel{xmlel | children: [insert_when(children, name, payload)]}
  end
end