lib/roles/peer.ex

defmodule Wampex.Roles.Peer do
  @moduledoc """
  Handles requests and responses for low-level Peer/Session interactions
  """
  alias Wampex.Role
  @behaviour Role

  @hello 1
  @welcome 2
  @abort 3
  @challenge 4
  @authenticate 5
  @goodbye 6
  @error 8
  @invocation 68

  defmodule Hello do
    @moduledoc false
    @enforce_keys [:realm, :roles]
    defstruct [:realm, :roles, options: %{}, agent: "WAMPex"]

    @type t :: %__MODULE__{
            realm: binary(),
            roles: nonempty_list(module()),
            agent: binary(),
            options: map()
          }
  end

  defmodule Welcome do
    @moduledoc false
    @enforce_keys [:session_id]
    defstruct [:session_id, options: %{}]

    @type t :: %__MODULE__{
            session_id: integer(),
            options: map()
          }
  end

  defmodule Challenge do
    @moduledoc false
    @enforce_keys [:auth_method]
    defstruct [:auth_method, extra: %{}]

    @type t :: %__MODULE__{
            auth_method: String.t(),
            extra: %{}
          }
  end

  defmodule Goodbye do
    @moduledoc false
    @enforce_keys [:reason]
    defstruct [:reason, options: %{}]

    @type t :: %__MODULE__{
            reason: binary(),
            options: map()
          }
  end

  defmodule Authenticate do
    @moduledoc false
    @enforce_keys [:signature, :extra]
    defstruct [:signature, :extra]

    @type t :: %__MODULE__{
            signature: binary(),
            extra: map()
          }
  end

  defmodule Abort do
    @moduledoc false
    @enforce_keys [:reason]
    defstruct [:reason, details: %{}]

    @type t :: %__MODULE__{
            reason: String.t(),
            details: map()
          }
  end

  defmodule Error do
    @moduledoc false
    @enforce_keys [:error]
    defstruct [:request_id, :type, :error, arg_list: [], arg_kw: %{}, details: %{}]

    @type t :: %__MODULE__{
            request_id: integer() | nil,
            type: integer() | nil,
            error: String.t(),
            arg_list: list(),
            arg_kw: map(),
            details: map()
          }
  end

  @impl true
  def add(roles), do: roles

  @spec hello(Hello.t()) :: Wampex.message()
  def hello(%Hello{realm: r, roles: roles, agent: agent, options: opts}) do
    options =
      Map.merge(opts, %{agent: agent, roles: Enum.reduce(roles, %{}, fn r, acc -> r.add(acc) end)})

    [@hello, r, options]
  end

  @spec welcome(Welcome.t()) :: Wampex.message()
  def welcome(%Welcome{session_id: si, options: opts}) do
    [@welcome, si, opts]
  end

  @spec abort(Abort.t()) :: Wampex.message()
  def abort(%Abort{reason: reason, details: opts}) do
    [@abort, opts, reason]
  end

  @spec challenge(Challenge.t()) :: Wampex.message()
  def challenge(%Challenge{auth_method: am, extra: ext}) do
    [@challenge, am, ext]
  end

  @spec goodbye(Goodbye.t()) :: Wampex.message()
  def goodbye(%Goodbye{reason: r, options: opts}) do
    [@goodbye, opts, r]
  end

  @spec authenticate(Authenticate.t()) :: Wampex.message()
  def authenticate(%Authenticate{signature: s, extra: e}) do
    [@authenticate, s, e]
  end

  @impl true
  def handle([@hello, realm, opts]) do
    {[{:next_event, :internal, :hello}], nil,
     {:update, :hello,
      %Hello{
        realm: realm,
        options: opts,
        roles: get_in(opts, ["roles"]),
        agent: get_in(opts, ["agent"])
      }}}
  end

  @impl true
  def handle([@authenticate, sig, extra]) do
    {[{:next_event, :internal, :authenticate}], nil,
     {:update, :authenticate, %Authenticate{signature: sig, extra: extra}}}
  end

  @impl true
  def handle([@welcome, session_id, _dets]) do
    {[{:next_event, :internal, :welcome}], nil, {:update, :id, session_id}}
  end

  @impl true
  def handle([@abort, _dets, reason]) do
    {[{:next_event, :internal, :abort}], nil, {:update, :error, reason}}
  end

  @impl true
  def handle([@goodbye, _dets, reason]) do
    {[{:next_event, :internal, :goodbye}], nil, {:update, :goodbye, reason}}
  end

  @impl true
  def handle([@challenge, auth_method, extra]) do
    {[{:next_event, :internal, :challenge}], nil,
     {:update, :challenge, %Challenge{auth_method: auth_method, extra: extra}}}
  end

  @impl true
  def handle([@error, type, id, dets, error]) do
    handle([@error, type, id, dets, error, [], %{}])
  end

  @impl true
  def handle([@error, type, id, dets, error, arg_l]) do
    handle([@error, type, id, dets, error, arg_l, %{}])
  end

  @impl true
  def handle([@error, @invocation, id, dets, err, arg_l, arg_kw]) do
    {[{:next_event, :internal, :invocation_error}], id,
     {:update, :error,
      %Error{
        type: @invocation,
        request_id: id,
        error: err,
        details: dets,
        arg_list: arg_l,
        arg_kw: arg_kw
      }}}
  end

  @impl true
  def handle([@error, type, id, dets, error, arg_l, arg_kw]) do
    {[{:next_event, :internal, :established}], id,
     %Error{
       type: type,
       request_id: id,
       error: error,
       details: dets,
       arg_list: arg_l,
       arg_kw: arg_kw
     }}
  end
end