defmodule Skogsra.Binding do
@moduledoc """
Variable binding behaviour.
"""
alias Skogsra.Env
alias Skogsra.Type
require Logger
@doc """
Callback for initializing binding.
"""
@callback init(env :: Env.t()) ::
{:ok, term()} | {:error, term()}
@doc """
Callback for getting an environment variable.
"""
@callback get_env(env :: Env.t(), config :: term()) ::
{:ok, term()} | {:error, term()}
@doc """
Uses the `Skogsra.Binding` behaviour.
"""
defmacro __using__(_) do
quote do
@behaviour Skogsra.Binding
@impl Skogsra.Binding
def init(_env) do
{:ok, nil}
end
defoverridable init: 1
end
end
@spec get_env(Env.binding(), env :: Env.t()) :: term()
def get_env(:system, %Env{} = env), do: get_env(Skogsra.Sys, env)
def get_env(:config, %Env{} = env), do: get_env(Skogsra.App, env)
def get_env(module, %Env{} = env) do
with {:ok, config} <- module.init(env),
{:ok, value} <- module.get_env(env, config) do
cast(module, env, value)
else
{:error, reason} ->
Logger.warning(reason)
nil
_ ->
nil
end
end
#########
# Helpers
@spec cast(module(), Env.t(), term()) :: nil | term()
defp cast(module, env, value) do
case Type.cast(env, value) do
{:ok, value} ->
value
:error ->
reason = """
Cannot cast #{inspect(value)} for environment variable #{inspect(env)}
Module: #{inspect(module)})
"""
Logger.warning(reason)
nil
end
end
end