defmodule Wampex.Client.Handler do
@moduledoc """
Macros and behaviours for client implementations
"""
@callback do_init(any()) :: %{client_name: atom(), registrations: map()}
require Logger
defmacro __using__(_opts) do
quote do
use GenServer
alias Wampex.Client
alias Wampex.Roles.Callee.{Unregister, Yield}
alias Wampex.Roles.Peer.Error
alias Wampex.Roles.Subscriber.Unsubscribe
require Logger
import unquote(__MODULE__)
@before_compile unquote(__MODULE__)
@procedures []
@topics []
def start_link(opts) do
{name, opts} = Keyword.pop(opts, :name)
GenServer.start_link(__MODULE__, opts, name: name)
end
@impl true
def init(opts) do
Process.flag(:trap_exit, true)
{cn, opts} = Keyword.pop(opts, :client_name)
state = do_init(opts)
Client.add(cn, self())
{:ok, add_client(state, cn)}
end
@impl true
def handle_continue({:registered, registrations}, state),
do: {:noreply, add_regs(state, registrations)}
@impl true
def terminate(_reason, %{
client_name: name,
registrations: %{subscriptions: subs, registrations: regs}
}) do
Enum.each(subs, fn id ->
Client.unsubscribe(name, %Unsubscribe{subscription_id: id})
end)
Enum.each(regs, fn id ->
Client.unregister(name, %Unregister{registration_id: id})
end)
end
@impl true
def terminate(_reason, _state), do: :ok
def do_init(_opts), do: %{client_name: nil, registrations: nil}
def handle_invocation_block({:ok, al, kw, state}, id) do
Client.yield(state.client_name, %Yield{request_id: id, arg_list: al, arg_kw: kw})
state
end
def handle_invocation_block({:error, error, al, kw, state}, id) do
Client.error(state.client_name, %Error{
request_id: id,
error: error,
arg_list: al,
arg_kw: kw
})
state
end
defp add_regs(state, regs) when is_struct(state) do
struct(state, %{registrations: regs})
end
defp add_regs(state, regs), do: Map.put(state, :registrations, regs)
defp add_client(state, cn) when is_struct(state) do
struct(state, %{client_name: cn})
end
defp add_client(state, cn), do: Map.put(state, :client_name, cn)
defoverridable start_link: 1, do_init: 1, handle_continue: 2, terminate: 2
end
end
defmacro __before_compile__(env) do
procedures = Module.get_attribute(env.module, :procedures) |> Enum.uniq()
topics = Module.get_attribute(env.module, :topics) |> Enum.uniq()
Logger.info(
"#{env.module} | Registering topics: #{inspect(topics)}, procedures: #{inspect(procedures)}"
)
quote do
require Logger
alias Wampex.Client
alias Wampex.Roles.Callee.Register
alias Wampex.Roles.Subscriber.Subscribe
@impl true
def handle_info({:connected, _}, %{client_name: cn} = state) do
Logger.info("Connected")
regs =
Enum.map(unquote(procedures), fn p ->
{:ok, reg} = Client.register(cn, %Register{procedure: p})
reg
end)
subs =
Enum.map(unquote(topics), fn
{t, match} ->
{:ok, proc} =
Client.subscribe(cn, %Subscribe{topic: t, options: %{"match" => match}})
proc
t ->
{:ok, reg} = Client.subscribe(cn, %Subscribe{topic: t})
reg
end)
Logger.info("Registered!")
{:noreply, state, {:continue, {:registered, %{registrations: regs, subscriptions: subs}}}}
end
end
end
defmacro invocation(procedure, list, kws, state, do: block) do
quote location: :keep do
alias Wampex.Roles.Dealer.Invocation
procs = Module.get_attribute(__MODULE__, :procedures)
Module.put_attribute(__MODULE__, :procedures, [unquote(procedure) | procs])
@impl true
def handle_info(
%Invocation{
request_id: id,
details: %{"procedure" => unquote(procedure)},
arg_list: unquote(list),
arg_kw: unquote(kws)
},
unquote(state) = state
) do
state = handle_invocation_block(unquote(block), id)
{:noreply, state}
end
end
end
defmacro event(topic, list, kws, state, do: block) do
quote location: :keep do
alias Wampex.Client
alias Wampex.Roles.Broker.Event
topics = Module.get_attribute(__MODULE__, :topics)
Module.put_attribute(__MODULE__, :topics, [unquote(topic) | topics])
@impl true
def handle_info(
%Event{
details: %{"topic" => unquote(topic)},
arg_list: unquote(list),
arg_kw: unquote(kws)
},
unquote(state) = state
) do
unquote(block)
end
end
end
end