defmodule Wampex.Client.Handler do
@moduledoc """
Macros and behaviours for client implementations
"""
@callback do_init(any()) :: %{client_name: atom()}
defmacro __using__(_opts) do
quote do
use GenServer
alias Wampex.Client
alias Wampex.Roles.Callee.Yield
alias Wampex.Roles.Peer.Error
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
{cn, opts} = Keyword.pop(opts, :client_name)
state = do_init(opts)
Client.add(cn, self())
{:ok, add_client(state, cn)}
end
defp add_client(state, cn) when is_struct(state) do
struct(state, %{client_name: cn})
end
defp add_client(state, cn), do: %{state | client_name: cn}
@impl true
def handle_continue({:registered, _}, state), do: {:noreply, state}
def do_init(_opts), do: %{client_name: 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
defoverridable do_init: 1, handle_continue: 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()
quote do
alias Wampex.Client
alias Wampex.Roles.Callee.Register
alias Wampex.Roles.Subscriber.Subscribe
@impl true
def handle_info({:connected, _}, %{client_name: cn} = state) do
regs =
Enum.map(unquote(procedures), fn p ->
Client.register(cn, %Register{procedure: p})
end)
subs =
Enum.map(unquote(topics), fn
{t, match} ->
Client.subscribe(cn, %Subscribe{topic: t, options: %{"match" => match}})
t ->
Client.subscribe(cn, %Subscribe{topic: t})
end)
{: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
state = unquote(block)
{:noreply, state}
end
end
end
end