defmodule Gnat.Server do require Logger @moduledoc """ A behavior for acting as a server for nats messages. You can use this behavior in your own module and then use the `Gnat.ConsumerSupervisor` to listen for and respond to nats messages. ## Example defmodule MyApp.RpcServer do use Gnat.Server def request(%{body: _body}) do {:reply, "hi"} end # defining an error handler is optional, the default one will just call Logger.error for you def error(%{gnat: gnat, reply_to: reply_to}, _error) do Gnat.pub(gnat, reply_to, "Something went wrong and I can't handle your request") end end """ @doc """ Called when a message is received from the broker """ @callback request(message :: Gnat.message()) :: :ok | {:reply, iodata()} | {:error, term()} @doc """ Called when an error occured during the `request/1` If your `request/1` function returned `{:error, term}`, then the `term` you returned will be passed as the second argument. If an exception was raised during your `request/1` function, then the exception will be passed as the second argument. If your `request/1` function returned something other than the supported return types, then its return value will be passed as the second argument. """ @callback error(message :: Gnat.message(), error :: term()) :: :ok | {:reply, iodata()} defmacro __using__(_opts) do quote do @behaviour Gnat.Server def error(_message, error) do require Logger Logger.error( "Gnat.Server encountered an error while handling a request: #{inspect(error)}", type: :gnat_server_error, error: error ) end defoverridable error: 2 end end # The functions below are not documented because they are used internally to run # the callback modules @doc false def execute(module, message) do try do case apply(module, :request, [message]) do :ok -> :done {:reply, data} -> send_reply(message, data) {:error, error} -> execute_error(module, message, error) other -> execute_error(module, message, other) end rescue e -> execute_error(module, message, e) end end @doc false defp execute_error(module, message, error) do try do case apply(module, :error, [message, error]) do :ok -> :done {:reply, data} -> send_reply(message, data) other -> Logger.error( "error handler for #{module} returned something unexpected: #{inspect(other)}", type: :gnat_server_error ) end rescue e -> Logger.error( "error handler for #{module} encountered an error: #{inspect(e)}", type: :gnat_server_error ) end end @doc false def send_reply(%{gnat: gnat, reply_to: return_address}, iodata) when is_binary(return_address) do Gnat.pub(gnat, return_address, iodata) end def send_reply(_other, _iodata) do Logger.error( "Could not send reply because no reply_to was provided with the original message", type: :gnat_server_error ) end end