./ct_report/coverage/pubsub_form_utils.COVER.html

1 %%%----------------------------------------------------------------------
2 %%% File : pubsub_form_utils.erl
3 %%% Author : Piotr Nosek <piotr.nosek@erlang-solutions.com>
4 %%% Purpose : mod_pubsub form processing utilities
5 %%% Created : 28 Nov 2018 by Piotr Nosek <piotr.nosek@erlang-solutions.com>
6
7 %%% Portions created by ProcessOne and Brian Cully <bjc@kublai.com>
8 %%%----------------------------------------------------------------------
9
10 -module(pubsub_form_utils).
11
12 -author("piotr.nosek@erlang-solutions.com").
13
14 -export([make_sub_xform/1, parse_sub_xform/1]).
15
16 -include("mongoose_logger.hrl").
17 -include("mongoose_ns.hrl").
18 -include_lib("exml/include/exml.hrl").
19
20 -type convert_from_binary_fun() :: fun(([binary()]) -> any()).
21 -type convert_to_binary_fun() :: fun((any()) -> [binary()]).
22 -type convert_funs() :: #{ from_binaries := convert_from_binary_fun(),
23 to_binaries := convert_to_binary_fun() }.
24 -type field_data_type() :: boolean | integer | datetime | list | atom | {custom, convert_funs()}.
25
26 -type option_properties() :: #{
27 form_type => binary(), % type reported as field attr in XML
28 possible_choices => [{Value :: binary(), ValueDescription :: binary()}],
29 data_type => field_data_type(),
30 label => binary()
31 }.
32
33 -type option_definition() :: {FormVar :: binary(),
34 InternalName :: atom(),
35 OptionProperties :: option_properties()}.
36
37 -type convert_from_binary_error() :: {error, {unknown_option, VarName :: binary}}.
38 -type parse_error() :: {error, invalid_form} | convert_from_binary_error().
39
40 %%====================================================================
41 %% API
42 %%====================================================================
43
44 %% Missing options won't have any <value/> elements
45 %% TODO: Right now
46 -spec make_sub_xform(Options :: mod_pubsub:subOptions()) -> {ok, exml:element()}.
47 make_sub_xform(Options) ->
48 8 XFields = [make_field_xml(OptDefinition, Options) || OptDefinition <- sub_form_options()],
49 8 {ok, make_sub_xform_xml(XFields)}.
50
51 %% The list of options returned by this function may be a subset of the options schema.
52 %% TODO: It is the behaviour of original code. Maybe it should be changed? To discuss.
53 -spec parse_sub_xform(exml:element() | undefined) -> {ok, mod_pubsub:subOptions()} | parse_error().
54 parse_sub_xform(undefined) ->
55 81 {ok, []};
56 parse_sub_xform(XForm) ->
57 14 case jlib:parse_xdata_submit(XForm) of
58
:-(
invalid -> {error, invalid_form};
59 14 XData -> convert_fields_from_binaries(XData, [], sub_form_options())
60 end.
61
62 %%====================================================================
63 %% Form XML builders
64 %%====================================================================
65
66 -spec make_sub_xform_xml(XFields :: [exml:element()]) -> exml:element().
67 make_sub_xform_xml(XFields) ->
68 8 FormTypeEl = #xmlel{name = <<"field">>,
69 attrs = [{<<"var">>, <<"FORM_TYPE">>}, {<<"type">>, <<"hidden">>}],
70 children = [#xmlel{name = <<"value">>, attrs = [],
71 children = [{xmlcdata, ?NS_PUBSUB_SUB_OPTIONS}]}]},
72 8 #xmlel{name = <<"x">>, attrs = [{<<"xmlns">>, ?NS_XDATA}], children = [FormTypeEl | XFields]}.
73
74 -spec make_field_xml(OptDefinition :: option_definition(),
75 Options :: mod_pubsub:subOptions()) -> exml:element().
76 make_field_xml({VarName, Key, #{ label := Label, form_type := FormType } = VarProps}, Options) ->
77 64 ChoicesEls = make_choices_xml(VarProps),
78 64 ValEls = make_values_xml(Key, Options, VarProps),
79
80 64 #xmlel{name = <<"field">>,
81 attrs = [{<<"var">>, VarName}, {<<"type">>, FormType}, {<<"label">>, Label}],
82 children = ChoicesEls ++ ValEls}.
83
84 make_choices_xml(#{ possible_choices := PossibleChoices }) ->
85 24 [ make_option_xml(Value, Label) || {Value, Label} <- PossibleChoices ];
86 make_choices_xml(#{}) ->
87 40 [].
88
89 make_option_xml(Value, Label) ->
90 72 #xmlel{name = <<"option">>, attrs = [{<<"label">>, Label}], children = [make_value_xml(Value)]}.
91
92 make_values_xml(Key, Options, #{ data_type := DataType }) ->
93 64 case lists:keyfind(Key, 1, Options) of
94 {_, Value} ->
95 8 [make_value_xml(BinVal) || BinVal <- convert_value_to_binaries(Value, DataType)];
96 false ->
97 56 []
98 end.
99
100 make_value_xml(Value) ->
101 80 #xmlel{name = <<"value">>, attrs = [], children = [#xmlcdata{ content = Value }]}.
102
103 %%====================================================================
104 %% Form definitions & conversions
105 %%====================================================================
106
107 -spec sub_form_options() -> [option_definition()].
108 sub_form_options() ->
109 22 [
110 {<<"pubsub#deliver">>, deliver,
111 #{ form_type => <<"boolean">>,
112 data_type => boolean,
113 label => <<"Whether an entity wants to receive or disable notifications">>
114 }},
115
116 {<<"pubsub#digest">>, digest,
117 #{ form_type => <<"boolean">>,
118 data_type => boolean,
119 label => <<"Whether an entity wants to receive digests (aggregations)"
120 " of notifications or all notifications individually">>
121 }},
122
123 {<<"pubsub#digest_frequency">>, digest_frequency,
124 #{ form_type => <<"text-single">>,
125 data_type => integer,
126 label => <<"The minimum number of milliseconds between sending"
127 " any two notification digests">>
128 }},
129
130 {<<"pubsub#expire">>, expire,
131 #{ form_type => <<"text-single">>,
132 data_type => datetime,
133 label => <<"The DateTime at which a leased subscription will end or has ended">>
134 }},
135
136 {<<"pubsub#include_body">>, include_body,
137 #{ form_type => <<"boolean">>,
138 data_type => boolean,
139 label => <<"Whether an entity wants to receive an XMPP message body"
140 " in addition to the payload format">>
141 }},
142
143 {<<"pubsub#show-values">>, show_values,
144 #{ form_type => <<"list-multi">>,
145 possible_choices => [{<<"away">>, <<"Away">>},
146 {<<"chat">>, <<"Chat">>},
147 {<<"dnd">>, <<"Do Not Disturb">>},
148 {<<"online">>, <<"Any online state">>},
149 {<<"xa">>, <<"Extended Away">>}],
150 data_type => list,
151 label => <<"The presence states for which an entity wants to receive notifications">>
152 }},
153
154 {<<"pubsub#subscription_type">>, subscription_type,
155 #{ form_type => <<"list-single">>,
156 possible_choices => [{<<"items">>, <<"Receive notification of new items only">>},
157 {<<"nodes">>, <<"Receive notification of new nodes only">>}],
158 data_type => {custom, #{ from_binaries => fun convert_sub_type_from_binary/1,
159 to_binaries => fun convert_sub_type_to_binary/1 }},
160 label => <<"Type of notification to receive">>
161 }},
162
163 {<<"pubsub#subscription_depth">>, subscription_depth,
164 #{ form_type => <<"list-single">>,
165 possible_choices => [{<<"1">>, <<"Receive notification from direct child nodes only">>},
166 {<<"all">>, <<"Receive notification from all descendent nodes">>}],
167 data_type => {custom, #{ from_binaries => fun convert_sub_depth_from_binary/1,
168 to_binaries => fun convert_sub_depth_to_binary/1 }},
169 label => <<"Depth from subscription for which to receive notifications">>
170 }}
171 ].
172
173 -spec convert_fields_from_binaries([{VarNameBin :: binary(), Values :: [binary()]}],
174 Acc :: mod_pubsub:subOptions(),
175 Schema :: [option_definition()]) ->
176 {ok, mod_pubsub:subOptions()} | convert_from_binary_error().
177 convert_fields_from_binaries([], Result, _Schema) ->
178 14 {ok, Result};
179 convert_fields_from_binaries([{<<"FORM_TYPE">>, _Values} | RData], Acc, Schema) ->
180 14 convert_fields_from_binaries(RData, Acc, Schema);
181 convert_fields_from_binaries([{VarBin, Values} | RData], Acc, Schema) ->
182 14 case lists:keyfind(VarBin, 1, Schema) of
183 {_VBin, _Var, #{ data_type := DataType }} when Values == [] andalso DataType /= list ->
184
:-(
convert_fields_from_binaries(RData, Acc, Schema);
185 {_VBin, Var, #{ data_type := DataType }} ->
186 14 try convert_value_from_binaries(Values, DataType) of
187 Converted ->
188 14 NAcc = lists:keystore(Var, 1, Acc, {Var, Converted}),
189 14 convert_fields_from_binaries(RData, NAcc, Schema)
190 catch
191 C:R:S ->
192
:-(
{error, {conversion_failed, {Var, DataType, C, R, S}}}
193 end;
194 false ->
195
:-(
{error, {unknown_option, VarBin}}
196 end.
197
198 -spec convert_value_from_binaries(Bins :: [binary()], field_data_type()) -> any().
199 convert_value_from_binaries(Bins, {custom, #{ from_binaries := ConvertFromBinaryFun }}) ->
200
:-(
ConvertFromBinaryFun(Bins);
201 convert_value_from_binaries([Bin], boolean) ->
202 14 convert_bool_from_binary(Bin);
203 convert_value_from_binaries([Bin], integer) ->
204
:-(
binary_to_integer(Bin);
205 convert_value_from_binaries([Bin], datetime) ->
206
:-(
calendar:rfc3339_to_system_time(binary_to_list(Bin), [{unit, microsecond}]);
207 convert_value_from_binaries(Bins, list) when is_list(Bins) ->
208
:-(
Bins.
209
210 -spec convert_value_to_binaries(Value :: any(), field_data_type()) -> [binary()].
211 convert_value_to_binaries(Value, {custom, #{ to_binaries := ConvertToBinaryFun }}) ->
212
:-(
ConvertToBinaryFun(Value);
213 convert_value_to_binaries(Value, boolean) ->
214 8 [convert_bool_to_binary(Value)];
215 convert_value_to_binaries(Value, integer) ->
216
:-(
[integer_to_binary(Value)];
217 convert_value_to_binaries(Value, datetime) ->
218
:-(
TS = calendar:system_time_to_rfc3339(Value, [{offset, "Z"}, {unit, microsecond}]),
219
:-(
list_to_binary(TS);
220 convert_value_to_binaries(Value, list) when is_list(Value) ->
221
:-(
Value.
222
223
:-(
convert_bool_from_binary(<<"0">>) -> false;
224
:-(
convert_bool_from_binary(<<"1">>) -> true;
225 8 convert_bool_from_binary(<<"false">>) -> false;
226 6 convert_bool_from_binary(<<"true">>) -> true.
227
228 2 convert_bool_to_binary(true) -> <<"true">>;
229 6 convert_bool_to_binary(false) -> <<"false">>.
230
231
:-(
convert_sub_depth_from_binary([<<"all">>]) -> all;
232
:-(
convert_sub_depth_from_binary([DepthBin]) -> binary_to_integer(DepthBin).
233
234
:-(
convert_sub_depth_to_binary(all) -> [<<"all">>];
235
:-(
convert_sub_depth_to_binary(Depth) -> [integer_to_binary(Depth)].
236
237
:-(
convert_sub_type_from_binary(<<"items">>) -> items;
238
:-(
convert_sub_type_from_binary(<<"nodes">>) -> nodes.
239
240
:-(
convert_sub_type_to_binary(items) -> <<"items">>;
241
:-(
convert_sub_type_to_binary(nodes) -> <<"nodes">>.
Line Hits Source