defmodule Mouth.Messanger do @moduledoc """ """ @cannot_call_directly_error """ cannot call Mouth.Messanger directly. Instead implement your own Messanger module with: use Mouth.Messanger, otp_app: :my_app """ require Logger defmacro __using__(opts) do quote bind_quoted: [opts: opts] do @behaviour Mouth.Messanger @spec deliver(Mouth.Message.t) :: Mouth.Message.t def deliver(message) do config = build_config() Mouth.Messanger.deliver(message, config) end def status(id) do config = build_config() Mouth.Messanger.status(id, config) end otp_app = Keyword.fetch!(opts, :otp_app) defp build_config, do: Mouth.Messanger.build_config(__MODULE__, unquote(otp_app)) end end @optional_callbacks init: 0 @callback init() :: {:ok, Keyword.t} | :ignore @doc false def deliver(_message) do raise @cannot_call_directly_error end def status(_) do raise @cannot_call_directly_error end @doc false def deliver(message, config) do message = validate_and_normalize(message, config.adapter) result = if message.to == [] do debug_unsent(message) {:error, "Empty recipient"} else debug_sent(message, config.adapter) config.adapter.deliver(message, config) end result end @doc false def status(id, config) do config.adapter.status(id, config) end defp debug_sent(message, adapter) do Logger.debug """ Sending message with #{inspect adapter}: #{inspect message, limit: :infinity} """ end defp debug_unsent(message) do Logger.debug """ Message was not sent because recipient is empty. Full message - #{inspect message, limit: :infinity} """ end defp validate_and_normalize(message, adapter) do validate(message, adapter) end defp validate(message, _adapter) do validate_recipients(message) end defp validate_recipients(%Mouth.Message{} = message) do if Enum.all?( Enum.map([:to], &Map.get(message, &1)), &nil_recipient?/1 ) do raise Mouth.NilRecipientsError, message else message end end defp nil_recipient?(nil), do: true defp nil_recipient?([]), do: false defp nil_recipient?([_] = recipients) do Enum.all?(recipients, &nil_recipient?/1) end defp nil_recipient?(_), do: false def build_config(messanger, otp_app) do otp_app |> get_application_config(messanger) |> Map.new |> handle_adapter_config end defp handle_adapter_config(base_config = %{adapter: adapter}) do adapter.handle_config(base_config) end defp get_application_config(otp_app, messanger) do {:ok, config} = if Code.ensure_loaded?(messanger) and function_exported?(messanger, :init, 0) do messanger.init else {:ok, Application.get_env(otp_app, messanger)} end config end end