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