Current section

Files

Jump to
amoc src amoc_config amoc_config_attributes.erl
Raw

src/amoc_config/amoc_config_attributes.erl

%%==============================================================================
%% Copyright 2023 Erlang Solutions Ltd.
%% Licensed under the Apache License, Version 2.0 (see LICENSE file)
%%==============================================================================
%% this module is responsible for parsing module attributes
%%==============================================================================
-module(amoc_config_attributes).
-include("amoc_config.hrl").
%% API
-export([get_module_configuration/2]).
-export([none/1, none/2]).
-ifdef(TEST).
-export([%% exported for testing only
get_module_attributes/2,
process_module_attributes/2]).
-endif.
-type maybe_module_attribute() :: module_attribute() | term().
-type maybe_verification_method() :: verification_method() | term().
-type maybe_update_method() :: update_method() | term().
-type attribute_name() :: required_variable | override_variable.
%% ------------------------------------------------------------------
%% API
%% ------------------------------------------------------------------
-spec get_module_configuration(attribute_name(), module()) ->
maybe_module_config().
get_module_configuration(AttrName, Module) ->
ScenarioAttributes = get_module_attributes(AttrName, Module),
process_module_attributes(Module, ScenarioAttributes).
-spec none(any()) -> true.
none(_) -> true.
-spec none(any(), any()) -> ok.
none(_, _) -> ok.
%% ------------------------------------------------------------------
%% Internal Function Definitions
%% ------------------------------------------------------------------
-spec get_module_attributes(attribute_name(), module()) -> [maybe_module_attribute()].
get_module_attributes(AttrName, Module) ->
ModuleAttributes = apply(Module, module_info, [attributes]),
RequiredVariables = proplists:get_all_values(AttrName, ModuleAttributes),
lists:append(RequiredVariables).
-spec process_module_attributes(module(), [maybe_module_attribute()]) ->
maybe_module_config().
process_module_attributes(Module, ScenarioAttributes) ->
Config = [process_var_attr(Module, Attr)
|| Attr <- ScenarioAttributes],
amoc_config_utils:maybe_error(invalid_attribute_format, Config).
-spec process_var_attr(module(), maybe_module_attribute()) ->
{ok, module_parameter()} | {error, reason()}.
process_var_attr(Module, Attr) ->
PipelineActions = [
{fun check_mandatory_fields/1, []},
{fun check_default_value/1, []},
{fun check_verification_method/1, []},
{fun check_update_method/1, []},
{fun make_module_parameter/2, [Module]}],
case amoc_config_utils:pipeline(PipelineActions, {ok, Attr}) of
{error, Reason} -> {error, add_original_attribute(Reason, Attr)};
{ok, Param} -> {ok, Param}
end.
-spec check_mandatory_fields(maybe_module_attribute()) ->
{ok, #{name := name(), description := string(), any() => any()}} | {error, reason()}.
check_mandatory_fields(#{description := List, name := Atom} = Attr) when is_atom(Atom),
is_list(List) ->
case io_lib:char_list(List) of
true -> {ok, Attr};
false -> {error, invalid_attribute}
end;
check_mandatory_fields(_Attr) ->
{error, invalid_attribute}.
-spec check_default_value(#{any() => any()}) ->
{ok, #{default_value := value(), any() => any()}}.
check_default_value(Attr) ->
DefaultValue = maps:get(default_value, Attr, undefined),
{ok, Attr#{default_value => DefaultValue}}.
-spec check_verification_method(#{any() => any()}) ->
{ok, #{verification := maybe_verification_fun(), any() => any()}} | {error, reason()}.
check_verification_method(Attr) ->
VerificationMethod = maps:get(verification, Attr, none),
case verification_fn(VerificationMethod) of
not_exported ->
{error, verification_method_not_exported};
invalid_method ->
{error, invalid_verification_method};
VerificationFn ->
{ok, Attr#{verification => VerificationFn}}
end.
-spec check_update_method(#{any() => any()}) ->
{ok, #{update := maybe_update_fun(), any() => any()}} | {error, reason()}.
check_update_method(Attr) ->
UpdateMethod = maps:get(update, Attr, read_only),
case update_fn(UpdateMethod) of
not_exported ->
{error, update_method_not_exported};
invalid_method ->
{error, invalid_update_method};
UpdateFn ->
{ok, Attr#{update => UpdateFn}}
end.
-spec make_module_parameter(#{name := name(),
description := string(),
default_value := value(),
verification := maybe_verification_fun(),
update := maybe_update_fun(),
any() => any()},
module()) ->
{ok, module_parameter()}.
make_module_parameter(#{name := Name, description := Description, default_value := Value,
verification := VerificationFn, update := UpdateFn}, Module) ->
{ok, #module_parameter{name = Name, mod = Module, description = Description, value = Value,
verification_fn = VerificationFn, update_fn = UpdateFn}}.
-spec verification_fn(maybe_verification_method()) ->
maybe_verification_fun() | not_exported | invalid_method.
verification_fn(none) ->
fun ?MODULE:none/1;
verification_fn([_ | _] = OneOf) ->
one_of_fun(OneOf);
verification_fn(Fun) when is_function(Fun, 1) ->
is_exported(Fun);
verification_fn({Module, Function, 1}) ->
is_exported(fun Module:Function/1);
verification_fn(_) ->
invalid_method.
-spec update_fn(maybe_update_method()) ->
maybe_update_fun() | not_exported | invalid_method | read_only.
update_fn(read_only) ->
read_only;
update_fn(none) ->
fun ?MODULE:none/2;
update_fn(Fun) when is_function(Fun, 2) ->
is_exported(Fun);
update_fn({Module, Function, 2}) ->
is_exported(fun Module:Function/2);
update_fn(_) ->
invalid_method.
-spec is_exported(function()) ->
function() | not_exported.
is_exported(Fn) ->
[{type, T}, {module, M}, {name, F}, {arity, A}] =
[erlang:fun_info(Fn, I) || I <- [type, module, name, arity]],
case {T, code:ensure_loaded(M)} of
{external, {module, M}} ->
case erlang:function_exported(M, F, A) of
true -> Fn;
false -> not_exported
end;
_ -> not_exported
end.
-spec one_of_fun(one_of()) -> verification_fun().
one_of_fun(OneOf) ->
fun(X) ->
case lists:member(X, OneOf) of
true -> true;
false -> {false, {not_one_of, OneOf}}
end
end.
add_original_attribute(Reason, Attr) when is_tuple(Reason) ->
list_to_tuple([Attr | tuple_to_list(Reason)]);
add_original_attribute(Reason, Attr) ->
{Attr, Reason}.