aboutsummaryrefslogtreecommitdiff
path: root/lib/ejabberd/config/attr.ex
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ejabberd/config/attr.ex')
-rw-r--r--lib/ejabberd/config/attr.ex119
1 files changed, 119 insertions, 0 deletions
diff --git a/lib/ejabberd/config/attr.ex b/lib/ejabberd/config/attr.ex
new file mode 100644
index 000000000..9d17b157d
--- /dev/null
+++ b/lib/ejabberd/config/attr.ex
@@ -0,0 +1,119 @@
+defmodule Ejabberd.Config.Attr do
+ @moduledoc """
+ Module used to work with the attributes parsed from
+ an elixir block (do...end).
+
+ Contains functions for extracting attrs from a block
+ and validation.
+ """
+
+ @type attr :: {atom(), any()}
+
+ @attr_supported [
+ active:
+ [type: :boolean, default: true],
+ git:
+ [type: :string, default: ""],
+ name:
+ [type: :string, default: ""],
+ opts:
+ [type: :list, default: []],
+ dependency:
+ [type: :list, default: []]
+ ]
+
+ @doc """
+ Takes a block with annotations and extracts the list
+ of attributes.
+ """
+ @spec extract_attrs_from_block_with_defaults(any()) :: [attr]
+ def extract_attrs_from_block_with_defaults(block) do
+ block
+ |> extract_attrs_from_block
+ |> put_into_list_if_not_already
+ |> insert_default_attrs_if_missing
+ end
+
+ @doc """
+ Takes an attribute or a list of attrs and validate them.
+
+ Returns a {:ok, attr} or {:error, attr, cause} for each of the attributes.
+ """
+ @spec validate([attr]) :: [{:ok, attr}] | [{:error, attr, atom()}]
+ def validate(attrs) when is_list(attrs), do: Enum.map(attrs, &valid_attr?/1)
+ def validate(attr), do: validate([attr]) |> List.first
+
+ @doc """
+ Returns the type of an attribute, given its name.
+ """
+ @spec get_type_for_attr(atom()) :: atom()
+ def get_type_for_attr(attr_name) do
+ @attr_supported
+ |> Keyword.get(attr_name)
+ |> Keyword.get(:type)
+ end
+
+ @doc """
+ Returns the default value for an attribute, given its name.
+ """
+ @spec get_default_for_attr(atom()) :: any()
+ def get_default_for_attr(attr_name) do
+ @attr_supported
+ |> Keyword.get(attr_name)
+ |> Keyword.get(:default)
+ end
+
+ # Private API
+
+ # Given an elixir block (do...end) returns a list with the annotations
+ # or a single annotation.
+ @spec extract_attrs_from_block(any()) :: [attr] | attr
+ defp extract_attrs_from_block({:__block__, [], attrs}), do: Enum.map(attrs, &extract_attrs_from_block/1)
+ defp extract_attrs_from_block({:@, _, [attrs]}), do: extract_attrs_from_block(attrs)
+ defp extract_attrs_from_block({attr_name, _, [value]}), do: {attr_name, value}
+ defp extract_attrs_from_block(nil), do: []
+
+ # In case extract_attrs_from_block returns a single attribute,
+ # then put it into a list. (Ensures attrs are always into a list).
+ @spec put_into_list_if_not_already([attr] | attr) :: [attr]
+ defp put_into_list_if_not_already(attrs) when is_list(attrs), do: attrs
+ defp put_into_list_if_not_already(attr), do: [attr]
+
+ # Given a list of attributes, it inserts the missing attribute with their
+ # default value.
+ @spec insert_default_attrs_if_missing([attr]) :: [attr]
+ defp insert_default_attrs_if_missing(attrs) do
+ Enum.reduce @attr_supported, attrs, fn({attr_name, _}, acc) ->
+ case Keyword.has_key?(acc, attr_name) do
+ true -> acc
+ false -> Keyword.put(acc, attr_name, get_default_for_attr(attr_name))
+ end
+ end
+ end
+
+ # Given an attribute, validates it and return a tuple with
+ # {:ok, attr} or {:error, attr, cause}
+ @spec valid_attr?(attr) :: {:ok, attr} | {:error, attr, atom()}
+ defp valid_attr?({attr_name, param} = attr) do
+ case Keyword.get(@attr_supported, attr_name) do
+ nil -> {:error, attr, :attr_not_supported}
+ [{:type, param_type} | _] -> case is_of_type?(param, param_type) do
+ true -> {:ok, attr}
+ false -> {:error, attr, :type_not_supported}
+ end
+ end
+ end
+
+ # Given an attribute value and a type, it returns a true
+ # if the value its of the type specified, false otherwise.
+
+ # Usefoul for checking if an attr value respects the type
+ # specified for the annotation.
+ @spec is_of_type?(any(), atom()) :: boolean()
+ defp is_of_type?(param, type) when type == :boolean and is_boolean(param), do: true
+ defp is_of_type?(param, type) when type == :string and is_bitstring(param), do: true
+ defp is_of_type?(param, type) when type == :list and is_list(param), do: true
+ defp is_of_type?(param, type) when type == :atom and is_atom(param), do: true
+ defp is_of_type?(_param, type) when type == :any, do: true
+ defp is_of_type?(_, _), do: false
+end