./ct_report/coverage/mongoose_acc.COVER.html

1 %%%-------------------------------------------------------------------
2 %%% File : mongoose_acc.erl
3 %%% Author : Piotr Nosek <piotr.nosek@erlang-solutions.com>
4 %%% Author : Bartlomiej Gorny <bartlomiej.gorny@erlang-solutions.com>
5 %%% Purpose : Mongoose accumulator implementation
6 %%% Created : 11 Sep 2018 by Piotr Nosek <piotr.nosek@erlang-solutions.com>
7 %%%
8 %%% NS:Key conventions:
9 %%% * hook:result should be used to return hook processing result
10 %%% * iq:* contains useful IQ metadata but must be provided by mongoose_iq.erl
11 %%%-------------------------------------------------------------------
12 -module(mongoose_acc).
13 -author("bartlomiej.gorny@erlang-solutions.com").
14 -author("piotr.nosek@erlang-solutions.com").
15
16 -include("jlib.hrl").
17
18 %% API
19 % Constructor
20 -export([new/1]).
21 % Access to built-in fields
22 -export([
23 ref/1,
24 timestamp/1,
25 lserver/1,
26 host_type/1,
27 element/1,
28 to_jid/1,
29 from_jid/1,
30 packet/1,
31 stanza_name/1,
32 stanza_type/1,
33 stanza_ref/1
34 ]).
35 % Stanza update
36 -export([update_stanza/2]).
37 % Access to namespaced fields
38 -export([
39 set/3,
40 set/4,
41 set_permanent/2,
42 set_permanent/3,
43 set_permanent/4,
44 append/4,
45 get_permanent_keys/1,
46 get_permanent_fields/1,
47 get/2,
48 get/3,
49 get/4,
50 delete/3,
51 delete/2,
52 delete_many/3
53 ]).
54 % Strip with or without stanza replacement
55 -export([strip/1, strip/2]).
56
57 -ignore_xref([delete/2, ref/1]).
58
59 %% Note about 'undefined' to_jid and from_jid: these are the special cases when JID may be
60 %% truly unknown: before a client is authorized.
61
62 -type line_number() :: non_neg_integer().
63 -type location() :: #{mfa := mfa(),
64 line := line_number(),
65 file := string()}.
66
67 -type stanza_metadata() :: #{
68 element := exml:element(),
69 from_jid := jid:jid() | undefined,
70 to_jid := jid:jid() | undefined,
71 name := binary(),
72 type := binary(),
73 ref := reference()
74 }.
75
76 %% If it is defined as -opaque then dialyzer fails
77 %% It's still valid in acc 2.0 and gain is probably not worth the effort
78 -type t() :: #{
79 mongoose_acc := true,
80 ref := reference(),
81 timestamp := integer(), %microsecond
82 origin_pid := pid(),
83 origin_location := location(),
84 stanza := stanza_metadata() | undefined,
85 lserver := jid:lserver(),
86 host_type := binary() | undefined,
87 non_strippable := [ns_key()],
88 ns_key() => Value :: any()
89 }.
90
91 -export_type([t/0]).
92
93 -type new_acc_params() :: #{
94 location := location(),
95 lserver := jid:lserver(),
96 element => exml:element() | undefined,
97 host_type => binary() | undefined, % optional
98 from_jid => jid:jid() | undefined, % optional
99 to_jid => jid:jid() | undefined % optional
100 }.
101
102 -type strip_params() :: #{
103 lserver := jid:lserver(),
104 element := exml:element(),
105 host_type => binary() | undefined, % optional
106 from_jid => jid:jid() | undefined, % optional
107 to_jid => jid:jid() | undefined % optional
108 }.
109
110 -type stanza_params() :: #{
111 element := exml:element(),
112 from_jid => jid:jid() | undefined, % optional
113 to_jid => jid:jid() | undefined, % optional
114 _ => _
115 }.
116
117 -type ns_key() :: {NS :: any(), Key :: any()}.
118 -type ns_key_value() :: {Namespace :: any(), K :: any(), V :: any()}.
119
120 %% --------------------------------------------------------
121 %% API
122 %% --------------------------------------------------------
123
124 -spec new(Params :: new_acc_params()) -> t().
125 new(#{ location := Location, lserver := LServer } = Params) ->
126 42600 Stanza = case maps:get(element, Params, undefined) of
127 8758 undefined -> undefined;
128 33842 _Element -> stanza_from_params(Params)
129 end,
130 42600 HostType = get_host_type(Params),
131 42600 #{
132 mongoose_acc => true,
133 ref => make_ref(),
134 timestamp => os:system_time(microsecond),
135 origin_pid => self(),
136 origin_location => Location,
137 stanza => Stanza,
138 lserver => LServer,
139 host_type => HostType,
140 %% The non_strippable elements must be unique.
141 %% This used to be represented with the sets module, but as the number of elements inserted
142 %% was too small, sets were themselves an overhead, and also annoying when printing
143 non_strippable => []
144 }.
145
146 -spec ref(Acc :: t()) -> reference().
147 ref(#{ mongoose_acc := true, ref := Ref }) ->
148 6 Ref.
149
150 -spec timestamp(Acc :: t()) -> integer().
151 timestamp(#{ mongoose_acc := true, timestamp := TS }) ->
152 848 TS.
153
154 -spec lserver(Acc :: t()) -> jid:lserver().
155 lserver(#{ mongoose_acc := true, lserver := LServer }) ->
156 21458 LServer.
157
158 -spec host_type(Acc :: t()) -> binary() | undefined.
159 host_type(#{ mongoose_acc := true, host_type := HostType }) ->
160 222448 HostType.
161
162 -spec element(Acc :: t()) -> exml:element() | undefined.
163 element(#{ mongoose_acc := true, stanza := #{ element := El } }) ->
164 70124 El;
165 element(#{ mongoose_acc := true }) ->
166
:-(
undefined.
167
168 -spec from_jid(Acc :: t()) -> jid:jid() | undefined.
169 from_jid(#{ mongoose_acc := true, stanza := #{ from_jid := FromJID } }) ->
170 47313 FromJID;
171 from_jid(#{ mongoose_acc := true }) ->
172
:-(
undefined.
173
174 -spec to_jid(Acc :: t()) -> jid:jid() | undefined.
175 to_jid(#{ mongoose_acc := true, stanza := #{ to_jid := ToJID } }) ->
176 16409 ToJID;
177 to_jid(#{ mongoose_acc := true }) ->
178
:-(
undefined.
179
180 -spec packet(Acc :: t()) -> ejabberd_c2s:packet() | undefined.
181 packet(#{ mongoose_acc := true, stanza := #{ to_jid := ToJID,
182 from_jid := FromJID,
183 element := El } }) ->
184 395 {FromJID, ToJID, El};
185 packet(#{ mongoose_acc := true }) ->
186
:-(
undefined.
187
188 -spec stanza_name(Acc :: t()) -> binary() | undefined.
189 stanza_name(#{ mongoose_acc := true, stanza := #{ name := Name } }) ->
190 29525 Name;
191 stanza_name(#{ mongoose_acc := true }) ->
192
:-(
undefined.
193
194 -spec stanza_type(Acc :: t()) -> binary() | undefined.
195 stanza_type(#{ mongoose_acc := true, stanza := #{ type := Type } }) ->
196 77213 Type;
197 stanza_type(#{ mongoose_acc := true }) ->
198 83 undefined.
199
200 -spec stanza_ref(Acc :: t()) -> reference() | undefined.
201 stanza_ref(#{ mongoose_acc := true, stanza := #{ ref := StanzaRef } }) ->
202 7067 StanzaRef;
203 stanza_ref(#{ mongoose_acc := true }) ->
204
:-(
undefined.
205
206 -spec update_stanza(NewStanzaParams :: stanza_params(), Acc :: t()) -> t().
207 update_stanza(NewStanzaParams, #{ mongoose_acc := true } = Acc) ->
208 38748 Acc#{ stanza := stanza_from_params(NewStanzaParams) }.
209
210 %% Values set with this function are discarded during 'strip' operation...
211 -spec set(Namespace :: any(), K :: any(), V :: any(), Acc :: t()) -> t().
212 set(NS, K, V, #{ mongoose_acc := true } = Acc) ->
213 123393 Acc#{ {NS, K} => V }.
214
215 -spec set(Namespace :: any(), [{K :: any(), V :: any()}], Acc :: t()) -> t().
216 set(NS, KVs, #{ mongoose_acc := true } = Acc) ->
217 22214 NSKVs = [ {{NS, K}, V} || {K, V} <- KVs ],
218 22214 Input = maps:from_list(NSKVs),
219 22214 maps:merge(Acc, Input).
220
221 -spec set([ns_key_value()], Acc :: t()) -> t().
222 set(NSKVs, #{ mongoose_acc := true } = Acc) ->
223 632 PropList = [ {{NS, K}, V} || {NS, K, V} <- NSKVs ],
224 632 Input = maps:from_list(PropList),
225 632 maps:merge(Acc, Input).
226
227 %% .. while these are not.
228 -spec set_permanent(Namespace :: any(), K :: any(), V :: any(), Acc :: t()) -> t().
229 set_permanent(NS, K, V, #{ mongoose_acc := true, non_strippable := NonStrippable } = Acc) ->
230 2056 Key = {NS, K},
231 2056 NewNonStrippable = [Key | lists:delete(Key, NonStrippable)],
232 2056 Acc#{ Key => V, non_strippable := NewNonStrippable }.
233
234 -spec set_permanent(Namespace :: any(), [{K :: any(), V :: any()}], Acc :: t()) -> t().
235 set_permanent(NS, KVs, #{mongoose_acc := true, non_strippable := NonStrippable} = Acc) ->
236 15571 NewKeys = [{NS, K} || {K, _V} <- KVs, not lists:member({NS, K}, NonStrippable)],
237 15571 set(NS, KVs, Acc#{non_strippable := NewKeys ++ NonStrippable }).
238
239 -spec set_permanent([ns_key_value()], Acc :: t()) -> t().
240 set_permanent(NSKVs, #{mongoose_acc := true, non_strippable := NonStrippable} = Acc) ->
241 632 NewKeys = [{NS, K} || {NS, K, _V} <- NSKVs, not lists:member({NS, K}, NonStrippable)],
242 632 set(NSKVs, Acc#{non_strippable := NewKeys ++ NonStrippable }).
243
244 -spec append(NS :: any(), Key :: any(), Val :: any() | [any()], Acc :: t()) -> t().
245 append(NS, Key, Val, Acc) ->
246 25174 OldVal = get(NS, Key, [], Acc),
247 25174 set(NS, Key, append(OldVal, Val), Acc).
248
249 -spec get_permanent_keys(Acc :: t()) -> [ns_key()].
250 get_permanent_keys(#{mongoose_acc := true, non_strippable := NonStrippable}) ->
251 682 NonStrippable.
252
253 -spec get_permanent_fields(Acc :: t()) -> [ns_key_value()].
254 get_permanent_fields(Acc) ->
255 682 [{NS, Key, mongoose_acc:get(NS, Key, Acc)} ||
256 682 {NS, Key} <- mongoose_acc:get_permanent_keys(Acc)].
257
258 -spec get(Namespace :: any(), Acc :: t()) -> [{K :: any(), V :: any()}].
259 get(NS, #{mongoose_acc := true} = Acc) ->
260 1024 Fn = fun({Namespace, K}, V, List) when Namespace =:= NS ->
261 80 [{K, V} | List];
262 (_, _, List) ->
263 11060 List
264 end,
265 1024 maps:fold(Fn, [], Acc).
266
267 -spec get(Namespace :: any(), K :: any(), Acc :: t()) -> V :: any().
268 get(NS, K, #{ mongoose_acc := true } = Acc) ->
269 67342 maps:get({NS, K}, Acc).
270
271 -spec get(Namespace :: any(), K :: any(), Default :: any(), Acc :: t()) -> V :: any().
272 get(NS, K, Default, #{ mongoose_acc := true } = Acc) ->
273 179628 maps:get({NS, K}, Acc, Default).
274
275 -spec delete(Namespace :: any(), K :: any(), Acc :: t()) -> t().
276 delete(NS, K, #{ mongoose_acc := true, non_strippable := NonStrippable } = Acc0) ->
277 3387 Key = {NS, K},
278 3387 Acc1 = maps:remove(Key, Acc0),
279 3387 Acc1#{ non_strippable := lists:delete(Key, NonStrippable) }.
280
281 -spec delete_many(Namespace :: any(), [K :: any()], Acc :: t()) -> t().
282 delete_many(NS, Keys, #{ mongoose_acc := true, non_strippable := NonStrippable } = Acc0) ->
283 375 KVs = [{NS, K} || K <- Keys],
284 375 Acc1 = maps:without(KVs, Acc0),
285 375 Acc1#{ non_strippable := lists:subtract(NonStrippable, KVs) }.
286
287 -spec delete(Namespace :: any(), Acc :: t()) -> t().
288 delete(NS, Acc) ->
289
:-(
KeyList = [K || {K, _} <- get(NS, Acc)],
290
:-(
delete_many(NS, KeyList, Acc).
291
292 -spec strip(Acc :: t()) -> t().
293 strip(#{ mongoose_acc := true, non_strippable := NonStrippable } = Acc) ->
294 21505 maps:with(NonStrippable ++ default_non_strippable(), Acc).
295
296 -spec strip(ParamsToOverwrite :: strip_params(), Acc :: t()) -> t().
297 strip(#{ lserver := NewLServer } = Params, Acc) ->
298 21434 StrippedAcc = strip(Acc),
299 21434 StrippedAcc#{ lserver := NewLServer, host_type := get_host_type(Params),
300 stanza := stanza_from_params(Params) }.
301
302 %% --------------------------------------------------------
303 %% Internal functions
304 %% --------------------------------------------------------
305 -spec get_host_type(new_acc_params() | strip_params()) -> binary() | undefined.
306 get_host_type(#{host_type := HostType}) ->
307 60405 HostType;
308 get_host_type(_) ->
309 3629 undefined.
310
311 -spec stanza_from_params(Params :: stanza_params() | strip_params()) ->
312 stanza_metadata().
313 stanza_from_params(#{ element := El } = Params) ->
314 94024 #{
315 element => El,
316 from_jid => jid_from_params(from_jid, <<"from">>, Params),
317 to_jid => jid_from_params(to_jid, <<"to">>, Params),
318 name => El#xmlel.name,
319 type => exml_query:attr(El, <<"type">>),
320 ref => make_ref()
321 }.
322
323 -spec jid_from_params(MapKey :: to_jid | from_jid,
324 StanzaAttrName :: binary(),
325 Params :: stanza_params()) -> jid:jid().
326 jid_from_params(to_jid, _, #{to_jid := Jid}) ->
327 89849 Jid;
328 jid_from_params(from_jid, _, #{from_jid := Jid}) ->
329 94002 Jid;
330 jid_from_params(_, StanzaAttrName, #{element := El}) ->
331 4197 #jid{} = jid:from_binary(exml_query:attr(El, StanzaAttrName)).
332
333 -spec default_non_strippable() -> [atom()].
334 default_non_strippable() ->
335 21505 [
336 mongoose_acc,
337 ref,
338 timestamp,
339 origin_pid,
340 origin_location,
341 stanza,
342 lserver,
343 host_type,
344 non_strippable
345 ].
346
347 -spec append(OldVal :: list(), Val :: list() | any()) -> list().
348 3505 append(OldVal, Val) when is_list(OldVal), is_list(Val) -> OldVal ++ Val;
349 21669 append(OldVal, Val) when is_list(OldVal) -> [Val | OldVal].
Line Hits Source