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