Files
n8n-openai-adapter/lib/elixir/lib/behaviour.ex
T

153 lines
4.2 KiB
Elixir

defmodule Behaviour do
@moduledoc """
Utilities for defining behaviour interfaces.
Behaviours can be referenced by other modules
to ensure they implement required callbacks.
For example, you can specify the `URI.Parser`
behaviour as follows:
defmodule URI.Parser do
use Behaviour
@doc "Parses the given URL"
defcallback parse(uri_info :: URI.t) :: URI.t
@doc "Defines a default port"
defcallback default_port() :: integer
end
And then a module may use it as:
defmodule URI.HTTP do
@behaviour URI.Parser
def default_port(), do: 80
def parse(info), do: info
end
If the behaviour changes or `URI.HTTP` does
not implement one of the callbacks, a warning
will be raised.
## Implementation
Since Erlang R15, behaviours must be defined via
`@callback` attributes. `defcallback` is a simple
mechanism that defines the `@callback` attribute
according to the given type specification. `defcallback` allows
documentation to be created for the callback and defines
a custom function signature.
The callbacks and their documentation can be retrieved
via the `__behaviour__` callback function.
"""
@doc """
Define a function callback according to the given type specification.
"""
defmacro defcallback(spec) do
do_defcallback(split_spec(spec, quote(do: term)), __CALLER__)
end
@doc """
Define a macro callback according to the given type specification.
"""
defmacro defmacrocallback(spec) do
do_defmacrocallback(split_spec(spec, quote(do: Macro.t)), __CALLER__)
end
defp split_spec({:when, _, [{:::, _, [spec, return]}, guard]}, _default) do
{spec, return, guard}
end
defp split_spec({:when, _, [spec, guard]}, default) do
{spec, default, guard}
end
defp split_spec({:::, _, [spec, return]}, _default) do
{spec, return, []}
end
defp split_spec(spec, default) do
{spec, default, []}
end
defp do_defcallback({spec, return, guards}, caller) do
case Macro.decompose_call(spec) do
{name, args} ->
do_callback(:def, name, args, name, length(args), args, return, guards, caller)
_ ->
raise ArgumentError, message: "invalid syntax in defcallback #{Macro.to_string(spec)}"
end
end
defp do_defmacrocallback({spec, return, guards}, caller) do
case Macro.decompose_call(spec) do
{name, args} ->
do_callback(:defmacro, :"MACRO-#{name}", [quote(do: env :: Macro.Env.t)|args],
name, length(args), args, return, guards, caller)
_ ->
raise ArgumentError, message: "invalid syntax in defmacrocallback #{Macro.to_string(spec)}"
end
end
defp do_callback(kind, name, args, docs_name, docs_arity, _docs_args, return, guards, caller) do
Enum.each args, fn
{:::, _, [left, right]} ->
ensure_not_default(left)
ensure_not_default(right)
left
other ->
ensure_not_default(other)
other
end
quote do
@callback unquote(name)(unquote_splicing(args)) :: unquote(return) when unquote(guards)
Behaviour.store_docs(__MODULE__, unquote(caller.line), unquote(kind),
unquote(docs_name), unquote(docs_arity))
end
end
defp ensure_not_default({:\\, _, [_, _]}) do
raise ArgumentError, message: "default arguments \\\\ not supported in defcallback/defmacrocallback"
end
defp ensure_not_default(_), do: :ok
@doc false
def store_docs(module, line, kind, name, arity) do
doc = Module.get_attribute module, :doc
Module.delete_attribute module, :doc
Module.put_attribute module, :behaviour_docs, {{name, arity}, line, kind, doc}
end
@doc false
defmacro __using__(_) do
quote do
Module.register_attribute(__MODULE__, :behaviour_docs, accumulate: true)
@before_compile unquote(__MODULE__)
import unquote(__MODULE__)
end
end
@doc false
defmacro __before_compile__(env) do
docs = if Code.compiler_options[:docs] do
Enum.reverse Module.get_attribute(env.module, :behaviour_docs)
end
quote do
@doc false
def __behaviour__(:callbacks) do
__MODULE__.behaviour_info(:callbacks)
end
def __behaviour__(:docs) do
unquote(Macro.escape(docs))
end
end
end
end