./ct_report/coverage/mongoose_config_parser.COVER.html

1 %% @doc Parsing and processing of MongooseIM config files
2 %% - parser backends: only 'toml'
3 %% - config state management
4 -module(mongoose_config_parser).
5
6 %% parser API
7 -export([parse_file/1]).
8
9 %% state API
10 -export([build_state/3]).
11
12 %% only for tests
13 -export([get_opts/1]).
14
15 -ignore_xref([get_opts/1]).
16
17 -callback parse_file(FileName :: string()) -> state().
18
19 -include("mongoose.hrl").
20
21 -export_type([state/0]).
22
23 -record(state, {opts = [] :: opts(),
24 hosts = [] :: [domain_name()],
25 host_types = [] :: [mongooseim:host_type()]}).
26
27 -type opts() :: [{mongoose_config:key(), mongoose_config:value()}].
28 -type domain_name() :: jid:server().
29 -type state() :: #state{}.
30
31 %% Parser API
32
33 -spec parse_file(FileName :: string()) -> opts().
34 parse_file(FileName) ->
35 93 ParserModule = parser_module(filename:extension(FileName)),
36 93 try ParserModule:parse_file(FileName) of
37 State ->
38 93 get_opts(State)
39 catch
40 error:{config_error, ExitMsg, Errors} ->
41
:-(
halt_with_msg(ExitMsg, Errors)
42 end.
43
44 %% Only the TOML format is supported
45 93 parser_module(".toml") -> mongoose_config_parser_toml.
46
47 %% State API
48
49 -spec build_state([jid:server()], [jid:server()], opts()) -> state().
50 build_state(Hosts, HostTypes, Opts) ->
51 93 lists:foldl(fun(F, StateIn) -> F(StateIn) end,
52 new_state(),
53 93 [fun(S) -> set_hosts(Hosts, S) end,
54 93 fun(S) -> set_host_types(HostTypes, S) end,
55 93 fun(S) -> set_opts(Opts, S) end,
56 fun unfold_globals/1,
57 fun post_process_services/1,
58 fun post_process_modules/1]).
59
60 -spec new_state() -> state().
61 new_state() ->
62 93 #state{}.
63
64 -spec set_opts(opts(), state()) -> state().
65 set_opts(Opts, State) ->
66 93 State#state{opts = Opts}.
67
68 -spec set_hosts([domain_name()], state()) -> state().
69 set_hosts(Hosts, State) ->
70 93 State#state{hosts = Hosts}.
71
72 -spec set_host_types([mongooseim:host_type()], state()) -> state().
73 set_host_types(HostTypes, State) ->
74 93 State#state{host_types = HostTypes}.
75
76 -spec get_opts(state()) -> opts().
77 get_opts(#state{opts = Opts}) ->
78 93 Opts.
79
80 %% Config post-processing
81
82 %% @doc Repeat global options for each host type for easier lookup
83 -spec unfold_globals(state()) -> state().
84 unfold_globals(Config = #state{opts = Opts, hosts = Hosts, host_types = HostTypes}) ->
85 93 {HTOpts, SimpleOpts} = lists:partition(fun({K, _}) -> is_tuple(K) end, Opts),
86 93 GroupedOpts = maps:to_list(group_opts(HTOpts)),
87 93 AllHostTypes = Hosts ++ HostTypes,
88 93 NewHTOpts = lists:flatmap(fun({K, M}) -> merge_opts(K, M, AllHostTypes) end, GroupedOpts),
89 93 Config#state{opts = SimpleOpts ++ NewHTOpts}.
90
91 %% @doc For each host type, merge the global value with the host-type value (if it exists)
92 -spec merge_opts(atom(), #{mongooseim:host_type_or_global() => mongoose_config:value()},
93 [mongooseim:host_type()]) ->
94 [{mongoose_config:host_type_key(), mongoose_config:value()}].
95 merge_opts(Key, Opts = #{global := GlobalValue}, AllHostTypes) ->
96 558 Global = case keep_global_value(Key) of
97 186 true -> [{{Key, global}, GlobalValue}];
98 372 false -> []
99 end,
100 558 Global ++ [{{Key, HT}, merge_values(Key, GlobalValue, maps:get(HT, Opts, GlobalValue))}
101 558 || HT <- AllHostTypes];
102 merge_opts(Key, Opts, _AllHostTypes) ->
103
:-(
[{{Key, HT}, Val} || {HT, Val} <- maps:to_list(Opts)].
104
105 %% @doc Group host-type options by keys for easier processing (key by key)
106 -spec group_opts([{mongoose_config:host_type_key(), mongoose_config:value()}]) ->
107 #{atom() => #{mongooseim:host_type_or_global() => mongoose_config:value()}}.
108 group_opts(HTOpts) ->
109 93 lists:foldl(fun({{Key, HT}, Val}, Acc) ->
110 923 maps:update_with(Key, fun(Opts) -> Opts#{HT => Val} end, #{HT => Val}, Acc)
111 end, #{}, HTOpts).
112
113 %% @doc Merge global options with host-type ones
114 -spec merge_values(atom(), mongoose_config:value(), mongoose_config:value()) ->
115 mongoose_config:value().
116 merge_values(acl, GlobalValue, HTValue) ->
117 508 merge_with(fun(V1, V2) -> V1 ++ V2 end, GlobalValue, HTValue);
118 merge_values(access, GlobalValue, HTValue) ->
119 508 merge_with(fun acl:merge_access_rules/2, GlobalValue, HTValue);
120 merge_values(_Key, _GlobalValue, HTValue) ->
121 2032 HTValue.
122
123 %% Use maps:merge_with/3 when dropping OTP 23
124 merge_with(F, GlobalMap, HTMap) ->
125 1016 maps:fold(fun(Key, HTVal, M) ->
126 10160 maps:update_with(Key, fun(GVal) when GVal =:= HTVal -> GVal;
127
:-(
(GVal) -> F(GVal, HTVal)
128 end, HTVal, M)
129 end, GlobalMap, HTMap).
130
131 %% @doc Global value is retained for access rules and acl as they can be matched on the global level
132 -spec keep_global_value(atom()) -> boolean().
133 93 keep_global_value(acl) -> true;
134 93 keep_global_value(access) -> true;
135 372 keep_global_value(_) -> false.
136
137 -spec post_process_services(state()) -> state().
138 post_process_services(State = #state{opts = Opts}) ->
139 93 Opts1 = lists:map(fun post_process_services_opt/1, Opts),
140 93 State#state{opts = Opts1}.
141
142 post_process_services_opt({services, Services}) ->
143 93 ServicesWithDeps = mongoose_service_deps:resolve_deps(Services),
144 93 {services, ServicesWithDeps};
145 post_process_services_opt(Other) ->
146 4958 Other.
147
148 -spec post_process_modules(state()) -> state().
149 post_process_modules(State = #state{opts = Opts}) ->
150 93 Opts1 = lists:map(fun post_process_modules_opt/1, Opts),
151 93 State#state{opts = Opts1}.
152
153 post_process_modules_opt({{modules, HostType}, Modules}) ->
154 508 ModulesWithDeps = gen_mod_deps:resolve_deps(HostType, Modules),
155 508 {{modules, HostType}, ModulesWithDeps};
156 post_process_modules_opt(Other) ->
157 4543 Other.
158
159 %% local functions
160
161 -spec halt_with_msg(string(), [any()]) -> no_return().
162 -ifdef(TEST).
163 halt_with_msg(ExitMsg, Errors) ->
164 error({config_error, ExitMsg, Errors}).
165 -else.
166 halt_with_msg(ExitMsg, Errors) ->
167
:-(
[?LOG_ERROR(Error) || Error <- Errors],
168
:-(
mongoose_config_utils:exit_or_halt(ExitMsg).
169 -endif.
Line Hits Source