defmodule Pow.Extension.Base do
@moduledoc """
Used to set up extensions to enable parts of extension for auto-discovery.
This exists to prevent unnecessary `Code.ensure_compiled/1` calls, and will
let the extension define what modules it has.
## Usage
defmodule MyCustomExtension do
use Pow.Extension.Base
@impl true
def ecto_schema?(), do: true
end
"""
@callback ecto_schema?() :: boolean()
@callback use_ecto_schema?() :: boolean()
@callback phoenix_controller_callbacks?() :: boolean()
@callback phoenix_messages?() :: boolean()
@callback phoenix_router?() :: boolean()
@callback phoenix_templates() :: [{binary(), [binary()]}]
@doc false
defmacro __using__(_opts) do
quote do
@behaviour unquote(__MODULE__)
@doc false
@impl true
def ecto_schema?(), do: false
@doc false
@impl true
def use_ecto_schema?(), do: false
@doc false
@impl true
def phoenix_controller_callbacks?(), do: false
@doc false
@impl true
def phoenix_messages?(), do: false
@doc false
@impl true
def phoenix_router?(), do: false
@doc false
@impl true
def phoenix_templates(), do: []
defoverridable unquote(__MODULE__)
end
end
@doc """
Checks whether an extension has a certain module.
If a base extension module doesn't exist, or is configured improperly,
`Code.ensure_compiled/1` will be used instead to see whether the module
exists for the extension.
"""
@spec has?(atom(), [any()]) :: boolean()
def has?(extension, module_list) do
try do
has_extension_module?(extension, module_list)
rescue
# TODO: Remove or refactor by 1.1.0
_e in UndefinedFunctionError ->
IO.warn("no #{inspect extension} base module to check for #{inspect module_list} support found, please use #{inspect __MODULE__} to implement it")
[extension]
|> Kernel.++(module_list)
|> Module.concat()
|> ensure_compiled?()
end
end
defp ensure_compiled?(module), do: match?({:module, ^module}, Code.ensure_compiled(module))
defp has_extension_module?(extension, ["Ecto", "Schema"]), do: extension.ecto_schema?()
defp has_extension_module?(extension, ["Phoenix", "ControllerCallbacks"]), do: extension.phoenix_controller_callbacks?()
defp has_extension_module?(extension, ["Phoenix", "Messages"]), do: extension.phoenix_messages?()
defp has_extension_module?(extension, ["Phoenix", "Router"]), do: extension.phoenix_router?()
@doc """
Checks whether an extension has a certain module that has a `__using__/1`
macro.
This calls `has?/2` first, If a base extension module doesn't exist, or is
configured improperly, `Kernel.macro_exported?/3` will be used instead to
check if the module has a `__using__/1` macro.
"""
@spec use?(atom(), [any()]) :: boolean()
def use?(extension, module_list) do
case has?(extension, module_list) do
true ->
try do
use_extension_module?(extension, module_list)
rescue
# TODO: Remove or refactor by 1.1.0
_e in UndefinedFunctionError ->
IO.warn("#{inspect extension} has been configured improperly")
[extension]
|> Kernel.++(module_list)
|> Module.concat()
|> Kernel.macro_exported?(:__using__, 1)
end
false ->
false
end
end
defp use_extension_module?(extension, ["Ecto", "Schema"]), do: extension.use_ecto_schema?()
end