defmodule Wampex.Client.Handler do @moduledoc """ Macros and behaviours for client implementations """ @callback do_init(any()) :: %{client_name: atom()} require Logger defmacro __using__(_opts) do quote do use GenServer alias Wampex.Client alias Wampex.Roles.Callee.Yield alias Wampex.Roles.Peer.Error 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 {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: Map.put(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() 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