./ct_report/coverage/mongoose_disco.COVER.html

1 %%% @doc This module is responsible for running disco_* hooks that collect information
2 %%% for service discovery. It also contains helpers for direct construction of XML elements
3 %%% representing the advertised services.
4
5 -module(mongoose_disco).
6
7 %% Hooks wrappers
8 -export([get_local_identity/5,
9 get_sm_identity/5,
10 get_local_items/5,
11 get_sm_items/5,
12 get_local_features/5,
13 get_sm_features/5,
14 get_muc_features/6,
15 get_info/4]).
16
17 %% Helpers used by hook handlers for Acc manipulation
18 -export([add_identities/2,
19 add_items/2,
20 add_features/2,
21 add_info/2]).
22
23 %% XML construction helpers
24 -export([identities_to_xml/1,
25 items_to_xml/1,
26 features_to_xml/1,
27 info_list_to_xml/1]).
28
29 -ignore_xref([items_to_xml/1]).
30
31 -include("jlib.hrl").
32
33 -type feature_acc() :: acc(feature()).
34 -type feature() :: binary().
35
36 -type item_acc() :: acc(item()).
37 -type item() :: #{jid := jid:lserver(), name => binary(), node => binary()}.
38
39 -type identity_acc() :: acc(identity()).
40 -type identity() :: #{category := binary(), type := binary(), name => binary()}.
41
42 -type info_acc() :: #{host_type := mongooseim:host_type(),
43 module := module() | undefined,
44 node := binary(),
45 lang := ejabberd:lang(),
46 result := empty | [info()]}.
47 -type info() :: #{xmlns := binary(), fields := [info_field()]}.
48 -type info_field() :: #{var := binary(), values := [binary()], label => binary()}.
49
50 -type acc(Elem) :: #{host_type := mongooseim:host_type(),
51 from_jid := jid:jid(),
52 to_jid := jid:jid(),
53 node := binary(),
54 lang := ejabberd:lang(),
55 result := empty | [Elem]}.
56
57 -export_type([item_acc/0, feature_acc/0, identity_acc/0,
58 item/0, feature/0, identity/0,
59 info_field/0, info/0]).
60
61 %% Hook wrapper API
62
63 -spec get_local_identity(mongooseim:host_type(), jid:jid(), jid:jid(), binary(), ejabberd:lang()) ->
64 [exml:element()].
65 get_local_identity(HostType, From, To, Node, Lang) ->
66 161 InitialAcc = new_acc(HostType, From, To, Node, Lang),
67 161 FinalAcc = mongoose_hooks:disco_local_identity(InitialAcc),
68 161 Identities = extract_result(FinalAcc),
69 161 identities_to_xml(Identities).
70
71 -spec get_sm_identity(mongooseim:host_type(), jid:jid(), jid:jid(), binary(), ejabberd:lang()) ->
72 [exml:element()].
73 get_sm_identity(HostType, From, To, Node, Lang) ->
74 11 InitialAcc = new_acc(HostType, From, To, Node, Lang),
75 11 IdentityAcc = mongoose_hooks:disco_sm_identity(InitialAcc),
76 11 Identities = extract_result(IdentityAcc),
77 11 identities_to_xml(Identities).
78
79 -spec get_local_items(mongooseim:host_type(), jid:jid(), jid:jid(), binary(), ejabberd:lang()) ->
80 {result, [exml:element()]} | empty.
81 get_local_items(HostType, From, To, Node, Lang) ->
82 31 Acc = new_acc(HostType, From, To, Node, Lang),
83 31 case mongoose_hooks:disco_local_items(Acc) of
84
:-(
#{result := empty} -> empty;
85 31 #{result := Items} -> {result, items_to_xml(Items)}
86 end.
87
88 -spec get_sm_items(mongooseim:host_type(), jid:jid(), jid:jid(), binary(), ejabberd:lang()) ->
89 {result, [exml:element()]} | empty.
90 get_sm_items(HostType, From, To, Node, Lang) ->
91 10 Acc = new_acc(HostType, From, To, Node, Lang),
92 10 case mongoose_hooks:disco_sm_items(Acc) of
93 4 #{result := empty} -> empty;
94 6 #{result := Items} -> {result, items_to_xml(Items)}
95 end.
96
97 -spec get_local_features(mongooseim:host_type(), jid:jid(), jid:jid(), binary(), ejabberd:lang()) ->
98 {result, [exml:element()]} | empty.
99 get_local_features(HostType, From, To, Node, Lang) ->
100 161 Acc = new_acc(HostType, From, To, Node, Lang),
101 161 case mongoose_hooks:disco_local_features(Acc) of
102
:-(
#{result := empty} -> empty;
103 161 #{result := Features} -> {result, features_to_xml(Features)}
104 end.
105
106 -spec get_sm_features(mongooseim:host_type(), jid:jid(), jid:jid(), binary(), ejabberd:lang()) ->
107 {result, [exml:element()]} | empty.
108 get_sm_features(HostType, From, To, Node, Lang) ->
109 11 Acc = new_acc(HostType, From, To, Node, Lang),
110 11 case mongoose_hooks:disco_sm_features(Acc) of
111
:-(
#{result := empty} -> empty;
112 11 #{result := Features} -> {result, features_to_xml(Features)}
113 end.
114
115 -spec get_muc_features(mongooseim:host_type(), jid:jid(), jid:jid(), binary(), ejabberd:lang(),
116 [mongoose_disco:feature()]) ->
117 [exml:element()].
118 get_muc_features(HostType, From, To, Node, Lang, ExtraFeatures) ->
119 15 InitialAcc = new_acc(HostType, From, To, Node, Lang),
120 15 FinalAcc = mongoose_hooks:disco_muc_features(InitialAcc),
121 15 Features = ExtraFeatures ++ extract_result(FinalAcc),
122 15 features_to_xml(Features).
123
124 -spec get_info(mongooseim:host_type(), module() | undefined, binary(), ejabberd:lang()) ->
125 [exml:element()].
126 get_info(HostType, Module, Node, Lang) ->
127 168 InitialAcc = new_info_acc(HostType, Module, Node, Lang),
128 168 FinalAcc = mongoose_hooks:disco_info(InitialAcc),
129 168 InfoList = extract_result(FinalAcc),
130 168 info_list_to_xml(InfoList).
131
132 %% Helper API
133
134 -spec add_identities([identity()], identity_acc()) -> identity_acc().
135 add_identities(Identities, Acc) ->
136 174 add(Identities, Acc).
137
138 -spec add_items([item()], item_acc()) -> item_acc().
139 add_items(Items, Acc) ->
140 82 add(Items, Acc).
141
142 -spec add_features([feature()], feature_acc()) -> feature_acc().
143 add_features(Features, Acc) ->
144 975 add(Features, Acc).
145
146 -spec add_info([info()], info_acc()) -> info_acc().
147 add_info(InfoList, Acc) ->
148 163 add(InfoList, Acc).
149
150 %% XML construction API
151
152 -spec identities_to_xml([identity()]) -> [exml:element()].
153 identities_to_xml(Identities) ->
154 195 lists:map(fun identity_to_xml/1, Identities).
155
156 -spec items_to_xml([item()]) -> [exml:element()].
157 items_to_xml(Items) ->
158 %% For each JID, leave only the rightmost item with that JID (the one which was added first).
159 %% This is needed as extension modules might add more detailed information about an item
160 %% than the default which is obtained from the registered routes and contains only the JID.
161 37 maps:values(maps:from_list([{JID, item_to_xml(Item)} || #{jid := JID} = Item <- Items])).
162
163 -spec features_to_xml([feature()]) -> [exml:element()].
164 features_to_xml(Features) ->
165 %% Avoid duplicating features
166 195 [feature_to_xml(Feature) || Feature <- lists:usort(Features)].
167
168 -spec info_list_to_xml([info()]) -> [exml:element()].
169 info_list_to_xml(InfoList) ->
170 173 [info_to_xml(Info) || Info <- InfoList].
171
172 %% Acc manipulation
173
174 -spec new_acc(mongooseim:host_type(), jid:jid(), jid:jid(), binary(), ejabberd:lang()) ->
175 acc(any()).
176 new_acc(HostType, From, To, Node, Lang) ->
177 400 #{host_type => HostType,
178 from_jid => From,
179 to_jid => To,
180 node => Node,
181 lang => Lang,
182 result => empty}.
183
184 -spec new_info_acc(mongooseim:host_type(), module(), binary(), ejabberd:lang()) -> info_acc().
185 new_info_acc(HostType, Module, Node, Lang) ->
186 168 #{host_type => HostType,
187 module => Module,
188 node => Node,
189 lang => Lang,
190 result => empty}.
191
192 -spec add([Elem], acc(Elem)) -> acc(Elem);
193 ([info()], info_acc()) -> info_acc().
194 add(Elements, Acc = #{result := empty}) ->
195 543 Acc#{result := Elements};
196 add(Elements, Acc = #{result := InitialElements}) ->
197 851 Acc#{result := Elements ++ InitialElements}.
198
199 -spec extract_result(acc(Elem)) -> [Elem];
200 (info_acc()) -> [info()].
201 15 extract_result(#{result := empty}) -> [];
202 340 extract_result(#{result := Elements}) -> Elements.
203
204 %% Conversion to XML
205
206 feature_to_xml(Feature) when is_binary(Feature) ->
207 6571 #xmlel{name = <<"feature">>, attrs = [{<<"var">>, Feature}]}.
208
209 item_to_xml(Item) ->
210 89 #xmlel{name = <<"item">>,
211 109 attrs = lists:map(fun({Key, Value}) -> {atom_to_binary(Key, utf8), Value} end,
212 maps:to_list(Item))}.
213
214 identity_to_xml(Identity) ->
215 199 #xmlel{name = <<"identity">>,
216 583 attrs = lists:map(fun({Key, Value}) -> {atom_to_binary(Key, utf8), Value} end,
217 maps:to_list(Identity))}.
218
219 -spec info_to_xml(info()) -> exml:element().
220 info_to_xml(#{xmlns := NS, fields := Fields}) ->
221 168 #xmlel{name = <<"x">>,
222 attrs = [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"result">>}],
223 children = [form_type_field_xml(NS) |
224 14 [info_field_to_xml(Field) || Field <- Fields]]}.
225
226 -spec info_field_to_xml(info_field()) -> exml:element().
227 info_field_to_xml(InfoField) ->
228 14 {Values, Attrs} = maps:take(values, InfoField),
229 14 #xmlel{name = <<"field">>,
230 24 attrs = lists:map(fun({Key, Value}) -> {atom_to_binary(Key, utf8), Value} end,
231 maps:to_list(Attrs)),
232 children = values_to_xml(Values)}.
233
234 -spec values_to_xml([binary()]) -> [exml:element()].
235 values_to_xml(Values) ->
236 14 [#xmlel{name = <<"value">>, children = [#xmlcdata{content = Value}]} || Value <- Values].
237
238 -spec form_type_field_xml(binary()) -> exml:element().
239 form_type_field_xml(NS) ->
240 168 #xmlel{name = <<"field">>,
241 attrs = [{<<"var">>, <<"FORM_TYPE">>}, {<<"type">>, <<"hidden">>}],
242 children = [#xmlel{name = <<"value">>,
243 children = [#xmlcdata{content = NS}]}]}.
Line Hits Source