The compile flag was introduced in EQM X 4.3 series where CE and EE code was diverged large enough which made non-practicle to determin edition at runtime. such approach made testing quite challenging as we'll have to build with different compile flags inorder to run per-edition test cases In this commit, we try to retrieve edition info from EMQX's description text, (put to PT for fast access) at runtime so we can test ALL editions from a super-set edition (EE).
921 lines
26 KiB
Erlang
921 lines
26 KiB
Erlang
%%--------------------------------------------------------------------
|
|
%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
|
|
%%
|
|
%% Licensed under the Apache License, Version 2.0 (the "License");
|
|
%% you may not use this file except in compliance with the License.
|
|
%% You may obtain a copy of the License at
|
|
%%
|
|
%% http://www.apache.org/licenses/LICENSE-2.0
|
|
%%
|
|
%% Unless required by applicable law or agreed to in writing, software
|
|
%% distributed under the License is distributed on an "AS IS" BASIS,
|
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
%% See the License for the specific language governing permissions and
|
|
%% limitations under the License.
|
|
%%--------------------------------------------------------------------
|
|
|
|
-module(emqx_rule_funcs).
|
|
|
|
-include("rule_engine.hrl").
|
|
-include_lib("emqx/include/emqx.hrl").
|
|
-include_lib("emqx/include/logger.hrl").
|
|
|
|
%% IoT Funcs
|
|
-export([ msgid/0
|
|
, qos/0
|
|
, flags/0
|
|
, flag/1
|
|
, topic/0
|
|
, topic/1
|
|
, clientid/0
|
|
, clientip/0
|
|
, peerhost/0
|
|
, username/0
|
|
, payload/0
|
|
, payload/1
|
|
, contains_topic/2
|
|
, contains_topic/3
|
|
, contains_topic_match/2
|
|
, contains_topic_match/3
|
|
, null/0
|
|
]).
|
|
|
|
%% Arithmetic Funcs
|
|
-export([ '+'/2
|
|
, '-'/2
|
|
, '*'/2
|
|
, '/'/2
|
|
, 'div'/2
|
|
, mod/2
|
|
, eq/2
|
|
]).
|
|
|
|
%% Math Funcs
|
|
-export([ abs/1
|
|
, acos/1
|
|
, acosh/1
|
|
, asin/1
|
|
, asinh/1
|
|
, atan/1
|
|
, atanh/1
|
|
, ceil/1
|
|
, cos/1
|
|
, cosh/1
|
|
, exp/1
|
|
, floor/1
|
|
, fmod/2
|
|
, log/1
|
|
, log10/1
|
|
, log2/1
|
|
, power/2
|
|
, round/1
|
|
, sin/1
|
|
, sinh/1
|
|
, sqrt/1
|
|
, tan/1
|
|
, tanh/1
|
|
]).
|
|
|
|
%% Bits Funcs
|
|
-export([ bitnot/1
|
|
, bitand/2
|
|
, bitor/2
|
|
, bitxor/2
|
|
, bitsl/2
|
|
, bitsr/2
|
|
, bitsize/1
|
|
, subbits/2
|
|
, subbits/3
|
|
, subbits/6
|
|
]).
|
|
|
|
%% Data Type Convertion
|
|
-export([ str/1
|
|
, str_utf8/1
|
|
, bool/1
|
|
, int/1
|
|
, float/1
|
|
, float/2
|
|
, map/1
|
|
, bin2hexstr/1
|
|
, hexstr2bin/1
|
|
]).
|
|
|
|
%% Data Type Validation Funcs
|
|
-export([ is_null/1
|
|
, is_not_null/1
|
|
, is_str/1
|
|
, is_bool/1
|
|
, is_int/1
|
|
, is_float/1
|
|
, is_num/1
|
|
, is_map/1
|
|
, is_array/1
|
|
]).
|
|
|
|
%% String Funcs
|
|
-export([ lower/1
|
|
, ltrim/1
|
|
, reverse/1
|
|
, rtrim/1
|
|
, strlen/1
|
|
, substr/2
|
|
, substr/3
|
|
, trim/1
|
|
, upper/1
|
|
, split/2
|
|
, split/3
|
|
, concat/2
|
|
, tokens/2
|
|
, tokens/3
|
|
, sprintf_s/2
|
|
, pad/2
|
|
, pad/3
|
|
, pad/4
|
|
, replace/3
|
|
, replace/4
|
|
, regex_match/2
|
|
, regex_replace/3
|
|
, ascii/1
|
|
, find/2
|
|
, find/3
|
|
]).
|
|
|
|
%% Map Funcs
|
|
-export([ map_new/0
|
|
]).
|
|
|
|
-export([ map_get/2
|
|
, map_get/3
|
|
, map_put/3
|
|
]).
|
|
|
|
%% For backword compatibility
|
|
-export([ mget/2
|
|
, mget/3
|
|
, mput/3
|
|
]).
|
|
|
|
%% Array Funcs
|
|
-export([ nth/2
|
|
, length/1
|
|
, sublist/2
|
|
, sublist/3
|
|
, first/1
|
|
, last/1
|
|
, contains/2
|
|
]).
|
|
|
|
%% Hash Funcs
|
|
-export([ md5/1
|
|
, sha/1
|
|
, sha256/1
|
|
]).
|
|
|
|
%% Data encode and decode
|
|
-export([ base64_encode/1
|
|
, base64_decode/1
|
|
, json_decode/1
|
|
, json_encode/1
|
|
, term_decode/1
|
|
, term_encode/1
|
|
]).
|
|
|
|
%% Date functions
|
|
-export([ now_rfc3339/0
|
|
, now_rfc3339/1
|
|
, unix_ts_to_rfc3339/1
|
|
, unix_ts_to_rfc3339/2
|
|
, rfc3339_to_unix_ts/1
|
|
, rfc3339_to_unix_ts/2
|
|
, now_timestamp/0
|
|
, now_timestamp/1
|
|
]).
|
|
|
|
%% Proc Dict Func
|
|
-export([ proc_dict_get/1
|
|
, proc_dict_put/2
|
|
, proc_dict_del/1
|
|
, kv_store_get/1
|
|
, kv_store_get/2
|
|
, kv_store_put/2
|
|
, kv_store_del/1
|
|
]).
|
|
|
|
-export(['$handle_undefined_function'/2]).
|
|
|
|
-compile({no_auto_import,
|
|
[ abs/1
|
|
, ceil/1
|
|
, floor/1
|
|
, round/1
|
|
, map_get/2
|
|
]}).
|
|
|
|
-define(is_var(X), is_binary(X)).
|
|
|
|
%% @doc "msgid()" Func
|
|
msgid() ->
|
|
fun(#{id := MsgId}) -> MsgId; (_) -> undefined end.
|
|
|
|
%% @doc "qos()" Func
|
|
qos() ->
|
|
fun(#{qos := QoS}) -> QoS; (_) -> undefined end.
|
|
|
|
%% @doc "topic()" Func
|
|
topic() ->
|
|
fun(#{topic := Topic}) -> Topic; (_) -> undefined end.
|
|
|
|
%% @doc "topic(N)" Func
|
|
topic(I) when is_integer(I) ->
|
|
fun(#{topic := Topic}) ->
|
|
lists:nth(I, emqx_topic:tokens(Topic));
|
|
(_) -> undefined
|
|
end.
|
|
|
|
%% @doc "flags()" Func
|
|
flags() ->
|
|
fun(#{flags := Flags}) -> Flags; (_) -> #{} end.
|
|
|
|
%% @doc "flags(Name)" Func
|
|
flag(Name) ->
|
|
fun(#{flags := Flags}) -> emqx_rule_maps:nested_get({var,Name}, Flags); (_) -> undefined end.
|
|
|
|
%% @doc "clientid()" Func
|
|
clientid() ->
|
|
fun(#{from := ClientId}) -> ClientId; (_) -> undefined end.
|
|
|
|
%% @doc "username()" Func
|
|
username() ->
|
|
fun(#{username := Username}) -> Username; (_) -> undefined end.
|
|
|
|
%% @doc "clientip()" Func
|
|
clientip() ->
|
|
peerhost().
|
|
|
|
peerhost() ->
|
|
fun(#{peerhost := Addr}) -> Addr; (_) -> undefined end.
|
|
|
|
payload() ->
|
|
fun(#{payload := Payload}) -> Payload; (_) -> undefined end.
|
|
|
|
payload(Path) ->
|
|
fun(#{payload := Payload}) when erlang:is_map(Payload) ->
|
|
emqx_rule_maps:nested_get(map_path(Path), Payload);
|
|
(_) -> undefined
|
|
end.
|
|
|
|
%% @doc Check if a topic_filter contains a specific topic
|
|
%% TopicFilters = [{<<"t/a">>, #{qos => 0}].
|
|
-spec(contains_topic(emqx_types:topic_filters(), emqx_types:topic())
|
|
-> true | false).
|
|
contains_topic(TopicFilters, Topic) ->
|
|
case find_topic_filter(Topic, TopicFilters, fun eq/2) of
|
|
not_found -> false;
|
|
_ -> true
|
|
end.
|
|
contains_topic(TopicFilters, Topic, QoS) ->
|
|
case find_topic_filter(Topic, TopicFilters, fun eq/2) of
|
|
{_, #{qos := QoS}} -> true;
|
|
_ -> false
|
|
end.
|
|
|
|
-spec(contains_topic_match(emqx_types:topic_filters(), emqx_types:topic())
|
|
-> true | false).
|
|
contains_topic_match(TopicFilters, Topic) ->
|
|
case find_topic_filter(Topic, TopicFilters, fun emqx_topic:match/2) of
|
|
not_found -> false;
|
|
_ -> true
|
|
end.
|
|
contains_topic_match(TopicFilters, Topic, QoS) ->
|
|
case find_topic_filter(Topic, TopicFilters, fun emqx_topic:match/2) of
|
|
{_, #{qos := QoS}} -> true;
|
|
_ -> false
|
|
end.
|
|
|
|
find_topic_filter(Filter, TopicFilters, Func) ->
|
|
try
|
|
[case Func(Topic, Filter) of
|
|
true -> throw(Result);
|
|
false -> ok
|
|
end || Result = #{topic := Topic} <- TopicFilters],
|
|
not_found
|
|
catch
|
|
throw:Result -> Result
|
|
end.
|
|
|
|
null() ->
|
|
undefined.
|
|
|
|
%%------------------------------------------------------------------------------
|
|
%% Arithmetic Funcs
|
|
%%------------------------------------------------------------------------------
|
|
|
|
%% plus 2 numbers
|
|
'+'(X, Y) when is_number(X), is_number(Y) ->
|
|
X + Y;
|
|
|
|
%% string concatenation
|
|
%% this requires one of the arguments is string, the other argument will be converted
|
|
%% to string automatically (implicit conversion)
|
|
'+'(X, Y) when is_binary(X); is_binary(Y) ->
|
|
concat(X, Y).
|
|
|
|
'-'(X, Y) when is_number(X), is_number(Y) ->
|
|
X - Y.
|
|
|
|
'*'(X, Y) when is_number(X), is_number(Y) ->
|
|
X * Y.
|
|
|
|
'/'(X, Y) when is_number(X), is_number(Y) ->
|
|
X / Y.
|
|
|
|
'div'(X, Y) when is_integer(X), is_integer(Y) ->
|
|
X div Y.
|
|
|
|
mod(X, Y) when is_integer(X), is_integer(Y) ->
|
|
X rem Y.
|
|
|
|
eq(X, Y) ->
|
|
X == Y.
|
|
|
|
%%------------------------------------------------------------------------------
|
|
%% Math Funcs
|
|
%%------------------------------------------------------------------------------
|
|
|
|
abs(N) when is_integer(N) ->
|
|
erlang:abs(N).
|
|
|
|
acos(N) when is_number(N) ->
|
|
math:acos(N).
|
|
|
|
acosh(N) when is_number(N) ->
|
|
math:acosh(N).
|
|
|
|
asin(N) when is_number(N)->
|
|
math:asin(N).
|
|
|
|
asinh(N) when is_number(N) ->
|
|
math:asinh(N).
|
|
|
|
atan(N) when is_number(N) ->
|
|
math:atan(N).
|
|
|
|
atanh(N) when is_number(N)->
|
|
math:atanh(N).
|
|
|
|
ceil(N) when is_number(N) ->
|
|
erlang:ceil(N).
|
|
|
|
cos(N) when is_number(N)->
|
|
math:cos(N).
|
|
|
|
cosh(N) when is_number(N) ->
|
|
math:cosh(N).
|
|
|
|
exp(N) when is_number(N)->
|
|
math:exp(N).
|
|
|
|
floor(N) when is_number(N) ->
|
|
erlang:floor(N).
|
|
|
|
fmod(X, Y) when is_number(X), is_number(Y) ->
|
|
math:fmod(X, Y).
|
|
|
|
log(N) when is_number(N) ->
|
|
math:log(N).
|
|
|
|
log10(N) when is_number(N) ->
|
|
math:log10(N).
|
|
|
|
log2(N) when is_number(N)->
|
|
math:log2(N).
|
|
|
|
power(X, Y) when is_number(X), is_number(Y) ->
|
|
math:pow(X, Y).
|
|
|
|
round(N) when is_number(N) ->
|
|
erlang:round(N).
|
|
|
|
sin(N) when is_number(N) ->
|
|
math:sin(N).
|
|
|
|
sinh(N) when is_number(N) ->
|
|
math:sinh(N).
|
|
|
|
sqrt(N) when is_number(N) ->
|
|
math:sqrt(N).
|
|
|
|
tan(N) when is_number(N) ->
|
|
math:tan(N).
|
|
|
|
tanh(N) when is_number(N) ->
|
|
math:tanh(N).
|
|
|
|
%%------------------------------------------------------------------------------
|
|
%% Bits Funcs
|
|
%%------------------------------------------------------------------------------
|
|
|
|
bitnot(I) when is_integer(I) ->
|
|
bnot I.
|
|
|
|
bitand(X, Y) when is_integer(X), is_integer(Y) ->
|
|
X band Y.
|
|
|
|
bitor(X, Y) when is_integer(X), is_integer(Y) ->
|
|
X bor Y.
|
|
|
|
bitxor(X, Y) when is_integer(X), is_integer(Y) ->
|
|
X bxor Y.
|
|
|
|
bitsl(X, I) when is_integer(X), is_integer(I) ->
|
|
X bsl I.
|
|
|
|
bitsr(X, I) when is_integer(X), is_integer(I) ->
|
|
X bsr I.
|
|
|
|
bitsize(Bits) when is_bitstring(Bits) ->
|
|
bit_size(Bits).
|
|
|
|
subbits(Bits, Len) when is_integer(Len), is_bitstring(Bits) ->
|
|
subbits(Bits, 1, Len).
|
|
|
|
subbits(Bits, Start, Len) when is_integer(Start), is_integer(Len), is_bitstring(Bits) ->
|
|
get_subbits(Bits, Start, Len, <<"integer">>, <<"unsigned">>, <<"big">>).
|
|
|
|
subbits(Bits, Start, Len, Type, Signedness, Endianness) when is_integer(Start), is_integer(Len), is_bitstring(Bits) ->
|
|
get_subbits(Bits, Start, Len, Type, Signedness, Endianness).
|
|
|
|
get_subbits(Bits, Start, Len, Type, Signedness, Endianness) ->
|
|
Begin = Start - 1,
|
|
case Bits of
|
|
<<_:Begin, Rem/bits>> when Rem =/= <<>> ->
|
|
Sz = bit_size(Rem),
|
|
do_get_subbits(Rem, Sz, Len, Type, Signedness, Endianness);
|
|
_ -> undefined
|
|
end.
|
|
|
|
-define(match_bits(Bits0, Pattern, ElesePattern),
|
|
case Bits0 of
|
|
Pattern ->
|
|
SubBits;
|
|
ElesePattern ->
|
|
SubBits
|
|
end).
|
|
do_get_subbits(Bits, Sz, Len, <<"integer">>, <<"unsigned">>, <<"big">>) ->
|
|
?match_bits(Bits, <<SubBits:Len/integer-unsigned-big-unit:1, _/bits>>,
|
|
<<SubBits:Sz/integer-unsigned-big-unit:1>>);
|
|
do_get_subbits(Bits, Sz, Len, <<"float">>, <<"unsigned">>, <<"big">>) ->
|
|
?match_bits(Bits, <<SubBits:Len/float-unsigned-big-unit:1, _/bits>>,
|
|
<<SubBits:Sz/float-unsigned-big-unit:1>>);
|
|
do_get_subbits(Bits, Sz, Len, <<"bits">>, <<"unsigned">>, <<"big">>) ->
|
|
?match_bits(Bits, <<SubBits:Len/bits-unsigned-big-unit:1, _/bits>>,
|
|
<<SubBits:Sz/bits-unsigned-big-unit:1>>);
|
|
|
|
do_get_subbits(Bits, Sz, Len, <<"integer">>, <<"signed">>, <<"big">>) ->
|
|
?match_bits(Bits, <<SubBits:Len/integer-signed-big-unit:1, _/bits>>,
|
|
<<SubBits:Sz/integer-signed-big-unit:1>>);
|
|
do_get_subbits(Bits, Sz, Len, <<"float">>, <<"signed">>, <<"big">>) ->
|
|
?match_bits(Bits, <<SubBits:Len/float-signed-big-unit:1, _/bits>>,
|
|
<<SubBits:Sz/float-signed-big-unit:1>>);
|
|
do_get_subbits(Bits, Sz, Len, <<"bits">>, <<"signed">>, <<"big">>) ->
|
|
?match_bits(Bits, <<SubBits:Len/bits-signed-big-unit:1, _/bits>>,
|
|
<<SubBits:Sz/bits-signed-big-unit:1>>);
|
|
|
|
do_get_subbits(Bits, Sz, Len, <<"integer">>, <<"unsigned">>, <<"little">>) ->
|
|
?match_bits(Bits, <<SubBits:Len/integer-unsigned-little-unit:1, _/bits>>,
|
|
<<SubBits:Sz/integer-unsigned-little-unit:1>>);
|
|
do_get_subbits(Bits, Sz, Len, <<"float">>, <<"unsigned">>, <<"little">>) ->
|
|
?match_bits(Bits, <<SubBits:Len/float-unsigned-little-unit:1, _/bits>>,
|
|
<<SubBits:Sz/float-unsigned-little-unit:1>>);
|
|
do_get_subbits(Bits, Sz, Len, <<"bits">>, <<"unsigned">>, <<"little">>) ->
|
|
?match_bits(Bits, <<SubBits:Len/bits-unsigned-little-unit:1, _/bits>>,
|
|
<<SubBits:Sz/bits-unsigned-little-unit:1>>);
|
|
|
|
do_get_subbits(Bits, Sz, Len, <<"integer">>, <<"signed">>, <<"little">>) ->
|
|
?match_bits(Bits, <<SubBits:Len/integer-signed-little-unit:1, _/bits>>,
|
|
<<SubBits:Sz/integer-signed-little-unit:1>>);
|
|
do_get_subbits(Bits, Sz, Len, <<"float">>, <<"signed">>, <<"little">>) ->
|
|
?match_bits(Bits, <<SubBits:Len/float-signed-little-unit:1, _/bits>>,
|
|
<<SubBits:Sz/float-signed-little-unit:1>>);
|
|
do_get_subbits(Bits, Sz, Len, <<"bits">>, <<"signed">>, <<"little">>) ->
|
|
?match_bits(Bits, <<SubBits:Len/bits-signed-little-unit:1, _/bits>>,
|
|
<<SubBits:Sz/bits-signed-little-unit:1>>).
|
|
|
|
%%------------------------------------------------------------------------------
|
|
%% Data Type Convertion Funcs
|
|
%%------------------------------------------------------------------------------
|
|
|
|
str(Data) ->
|
|
emqx_plugin_libs_rule:bin(Data).
|
|
|
|
str_utf8(Data) ->
|
|
emqx_plugin_libs_rule:utf8_bin(Data).
|
|
|
|
bool(Data) ->
|
|
emqx_plugin_libs_rule:bool(Data).
|
|
|
|
int(Data) ->
|
|
emqx_plugin_libs_rule:int(Data).
|
|
|
|
float(Data) ->
|
|
emqx_plugin_libs_rule:float(Data).
|
|
|
|
float(Data, Decimals) when Decimals > 0 ->
|
|
Data1 = ?MODULE:float(Data),
|
|
list_to_float(float_to_list(Data1, [{decimals, Decimals}])).
|
|
|
|
map(Data) ->
|
|
emqx_plugin_libs_rule:map(Data).
|
|
|
|
bin2hexstr(Bin) when is_binary(Bin) ->
|
|
emqx_misc:bin2hexstr_A_F(Bin).
|
|
|
|
hexstr2bin(Str) when is_binary(Str) ->
|
|
emqx_misc:hexstr2bin(Str).
|
|
|
|
%%------------------------------------------------------------------------------
|
|
%% NULL Funcs
|
|
%%------------------------------------------------------------------------------
|
|
|
|
is_null(undefined) -> true;
|
|
is_null(_Data) -> false.
|
|
|
|
is_not_null(Data) ->
|
|
not is_null(Data).
|
|
|
|
is_str(T) when is_binary(T) -> true;
|
|
is_str(_) -> false.
|
|
|
|
is_bool(T) when is_boolean(T) -> true;
|
|
is_bool(_) -> false.
|
|
|
|
is_int(T) when is_integer(T) -> true;
|
|
is_int(_) -> false.
|
|
|
|
is_float(T) when erlang:is_float(T) -> true;
|
|
is_float(_) -> false.
|
|
|
|
is_num(T) when is_number(T) -> true;
|
|
is_num(_) -> false.
|
|
|
|
is_map(T) when erlang:is_map(T) -> true;
|
|
is_map(_) -> false.
|
|
|
|
is_array(T) when is_list(T) -> true;
|
|
is_array(_) -> false.
|
|
|
|
%%------------------------------------------------------------------------------
|
|
%% String Funcs
|
|
%%------------------------------------------------------------------------------
|
|
|
|
lower(S) when is_binary(S) ->
|
|
string:lowercase(S).
|
|
|
|
ltrim(S) when is_binary(S) ->
|
|
string:trim(S, leading).
|
|
|
|
reverse(S) when is_binary(S) ->
|
|
iolist_to_binary(string:reverse(S)).
|
|
|
|
rtrim(S) when is_binary(S) ->
|
|
string:trim(S, trailing).
|
|
|
|
strlen(S) when is_binary(S) ->
|
|
string:length(S).
|
|
|
|
substr(S, Start) when is_binary(S), is_integer(Start) ->
|
|
string:slice(S, Start).
|
|
|
|
substr(S, Start, Length) when is_binary(S),
|
|
is_integer(Start),
|
|
is_integer(Length) ->
|
|
string:slice(S, Start, Length).
|
|
|
|
trim(S) when is_binary(S) ->
|
|
string:trim(S).
|
|
|
|
upper(S) when is_binary(S) ->
|
|
string:uppercase(S).
|
|
|
|
split(S, P) when is_binary(S),is_binary(P) ->
|
|
[R || R <- string:split(S, P, all), R =/= <<>> andalso R =/= ""].
|
|
|
|
split(S, P, <<"notrim">>) ->
|
|
string:split(S, P, all);
|
|
|
|
split(S, P, <<"leading_notrim">>) ->
|
|
string:split(S, P, leading);
|
|
split(S, P, <<"leading">>) when is_binary(S),is_binary(P) ->
|
|
[R || R <- string:split(S, P, leading), R =/= <<>> andalso R =/= ""];
|
|
split(S, P, <<"trailing_notrim">>) ->
|
|
string:split(S, P, trailing);
|
|
split(S, P, <<"trailing">>) when is_binary(S),is_binary(P) ->
|
|
[R || R <- string:split(S, P, trailing), R =/= <<>> andalso R =/= ""].
|
|
|
|
tokens(S, Separators) ->
|
|
[list_to_binary(R) || R <- string:lexemes(binary_to_list(S), binary_to_list(Separators))].
|
|
|
|
tokens(S, Separators, <<"nocrlf">>) ->
|
|
[list_to_binary(R) || R <- string:lexemes(binary_to_list(S), binary_to_list(Separators) ++ [$\r,$\n,[$\r,$\n]])].
|
|
|
|
%% implicit convert args to strings, and then do concatenation
|
|
concat(S1, S2) ->
|
|
unicode:characters_to_binary([str(S1), str(S2)], unicode).
|
|
|
|
sprintf_s(Format, Args) when is_list(Args) ->
|
|
erlang:iolist_to_binary(io_lib:format(binary_to_list(Format), Args)).
|
|
|
|
pad(S, Len) when is_binary(S), is_integer(Len) ->
|
|
iolist_to_binary(string:pad(S, Len, trailing)).
|
|
|
|
pad(S, Len, <<"trailing">>) when is_binary(S), is_integer(Len) ->
|
|
iolist_to_binary(string:pad(S, Len, trailing));
|
|
|
|
pad(S, Len, <<"both">>) when is_binary(S), is_integer(Len) ->
|
|
iolist_to_binary(string:pad(S, Len, both));
|
|
|
|
pad(S, Len, <<"leading">>) when is_binary(S), is_integer(Len) ->
|
|
iolist_to_binary(string:pad(S, Len, leading)).
|
|
|
|
pad(S, Len, <<"trailing">>, Char) when is_binary(S), is_integer(Len), is_binary(Char) ->
|
|
Chars = unicode:characters_to_list(Char, utf8),
|
|
iolist_to_binary(string:pad(S, Len, trailing, Chars));
|
|
|
|
pad(S, Len, <<"both">>, Char) when is_binary(S), is_integer(Len), is_binary(Char) ->
|
|
Chars = unicode:characters_to_list(Char, utf8),
|
|
iolist_to_binary(string:pad(S, Len, both, Chars));
|
|
|
|
pad(S, Len, <<"leading">>, Char) when is_binary(S), is_integer(Len), is_binary(Char) ->
|
|
Chars = unicode:characters_to_list(Char, utf8),
|
|
iolist_to_binary(string:pad(S, Len, leading, Chars)).
|
|
|
|
replace(SrcStr, P, RepStr) when is_binary(SrcStr), is_binary(P), is_binary(RepStr) ->
|
|
iolist_to_binary(string:replace(SrcStr, P, RepStr, all)).
|
|
|
|
replace(SrcStr, P, RepStr, <<"all">>) when is_binary(SrcStr), is_binary(P), is_binary(RepStr) ->
|
|
iolist_to_binary(string:replace(SrcStr, P, RepStr, all));
|
|
|
|
replace(SrcStr, P, RepStr, <<"trailing">>) when is_binary(SrcStr), is_binary(P), is_binary(RepStr) ->
|
|
iolist_to_binary(string:replace(SrcStr, P, RepStr, trailing));
|
|
|
|
replace(SrcStr, P, RepStr, <<"leading">>) when is_binary(SrcStr), is_binary(P), is_binary(RepStr) ->
|
|
iolist_to_binary(string:replace(SrcStr, P, RepStr, leading)).
|
|
|
|
regex_match(Str, RE) ->
|
|
case re:run(Str, RE, [global,{capture,none}]) of
|
|
match -> true;
|
|
nomatch -> false
|
|
end.
|
|
|
|
regex_replace(SrcStr, RE, RepStr) ->
|
|
re:replace(SrcStr, RE, RepStr, [global, {return,binary}]).
|
|
|
|
ascii(Char) when is_binary(Char) ->
|
|
[FirstC| _] = binary_to_list(Char),
|
|
FirstC.
|
|
|
|
find(S, P) when is_binary(S), is_binary(P) ->
|
|
find_s(S, P, leading).
|
|
|
|
find(S, P, <<"trailing">>) when is_binary(S), is_binary(P) ->
|
|
find_s(S, P, trailing);
|
|
|
|
find(S, P, <<"leading">>) when is_binary(S), is_binary(P) ->
|
|
find_s(S, P, leading).
|
|
|
|
find_s(S, P, Dir) ->
|
|
case string:find(S, P, Dir) of
|
|
nomatch -> <<"">>;
|
|
SubStr -> SubStr
|
|
end.
|
|
|
|
%%------------------------------------------------------------------------------
|
|
%% Array Funcs
|
|
%%------------------------------------------------------------------------------
|
|
|
|
nth(N, L) when is_integer(N), is_list(L) ->
|
|
lists:nth(N, L).
|
|
|
|
length(List) when is_list(List) ->
|
|
erlang:length(List).
|
|
|
|
sublist(Len, List) when is_integer(Len), is_list(List) ->
|
|
lists:sublist(List, Len).
|
|
|
|
sublist(Start, Len, List) when is_integer(Start), is_integer(Len), is_list(List) ->
|
|
lists:sublist(List, Start, Len).
|
|
|
|
first(List) when is_list(List) ->
|
|
hd(List).
|
|
|
|
last(List) when is_list(List) ->
|
|
lists:last(List).
|
|
|
|
contains(Elm, List) when is_list(List) ->
|
|
lists:member(Elm, List).
|
|
|
|
map_new() ->
|
|
#{}.
|
|
|
|
map_get(Key, Map) ->
|
|
map_get(Key, Map, undefined).
|
|
|
|
map_get(Key, Map, Default) ->
|
|
emqx_rule_maps:nested_get(map_path(Key), Map, Default).
|
|
|
|
map_put(Key, Val, Map) ->
|
|
emqx_rule_maps:nested_put(map_path(Key), Val, Map).
|
|
|
|
mget(Key, Map) ->
|
|
mget(Key, Map, undefined).
|
|
|
|
mget(Key, Map, Default) ->
|
|
case maps:find(Key, Map) of
|
|
{ok, Val} -> Val;
|
|
error when is_atom(Key) ->
|
|
%% the map may have an equivalent binary-form key
|
|
BinKey = emqx_plugin_libs_rule:bin(Key),
|
|
case maps:find(BinKey, Map) of
|
|
{ok, Val} -> Val;
|
|
error -> Default
|
|
end;
|
|
error when is_binary(Key) ->
|
|
try %% the map may have an equivalent atom-form key
|
|
AtomKey = list_to_existing_atom(binary_to_list(Key)),
|
|
case maps:find(AtomKey, Map) of
|
|
{ok, Val} -> Val;
|
|
error -> Default
|
|
end
|
|
catch error:badarg ->
|
|
Default
|
|
end;
|
|
error ->
|
|
Default
|
|
end.
|
|
|
|
mput(Key, Val, Map) ->
|
|
case maps:find(Key, Map) of
|
|
{ok, _} -> maps:put(Key, Val, Map);
|
|
error when is_atom(Key) ->
|
|
%% the map may have an equivalent binary-form key
|
|
BinKey = emqx_plugin_libs_rule:bin(Key),
|
|
case maps:find(BinKey, Map) of
|
|
{ok, _} -> maps:put(BinKey, Val, Map);
|
|
error -> maps:put(Key, Val, Map)
|
|
end;
|
|
error when is_binary(Key) ->
|
|
try %% the map may have an equivalent atom-form key
|
|
AtomKey = list_to_existing_atom(binary_to_list(Key)),
|
|
case maps:find(AtomKey, Map) of
|
|
{ok, _} -> maps:put(AtomKey, Val, Map);
|
|
error -> maps:put(Key, Val, Map)
|
|
end
|
|
catch error:badarg ->
|
|
maps:put(Key, Val, Map)
|
|
end;
|
|
error ->
|
|
maps:put(Key, Val, Map)
|
|
end.
|
|
|
|
%%------------------------------------------------------------------------------
|
|
%% Hash Funcs
|
|
%%------------------------------------------------------------------------------
|
|
|
|
md5(S) when is_binary(S) ->
|
|
hash(md5, S).
|
|
|
|
sha(S) when is_binary(S) ->
|
|
hash(sha, S).
|
|
|
|
sha256(S) when is_binary(S) ->
|
|
hash(sha256, S).
|
|
|
|
hash(Type, Data) ->
|
|
emqx_misc:bin2hexstr_a_f(crypto:hash(Type, Data)).
|
|
|
|
%%------------------------------------------------------------------------------
|
|
%% Data encode and decode Funcs
|
|
%%------------------------------------------------------------------------------
|
|
|
|
base64_encode(Data) when is_binary(Data) ->
|
|
base64:encode(Data).
|
|
|
|
base64_decode(Data) when is_binary(Data) ->
|
|
base64:decode(Data).
|
|
|
|
json_encode(Data) ->
|
|
emqx_json:encode(Data).
|
|
|
|
json_decode(Data) ->
|
|
emqx_json:decode(Data, [return_maps]).
|
|
|
|
term_encode(Term) ->
|
|
erlang:term_to_binary(Term).
|
|
|
|
term_decode(Data) when is_binary(Data) ->
|
|
erlang:binary_to_term(Data).
|
|
|
|
%%------------------------------------------------------------------------------
|
|
%% Dict Funcs
|
|
%%------------------------------------------------------------------------------
|
|
|
|
-define(DICT_KEY(KEY), {'@rule_engine', KEY}).
|
|
proc_dict_get(Key) ->
|
|
erlang:get(?DICT_KEY(Key)).
|
|
|
|
proc_dict_put(Key, Val) ->
|
|
erlang:put(?DICT_KEY(Key), Val).
|
|
|
|
proc_dict_del(Key) ->
|
|
erlang:erase(?DICT_KEY(Key)).
|
|
|
|
kv_store_put(Key, Val) ->
|
|
ets:insert(?KV_TAB, {Key, Val}).
|
|
|
|
kv_store_get(Key) ->
|
|
kv_store_get(Key, undefined).
|
|
kv_store_get(Key, Default) ->
|
|
case ets:lookup(?KV_TAB, Key) of
|
|
[{_, Val}] -> Val;
|
|
_ -> Default
|
|
end.
|
|
|
|
kv_store_del(Key) ->
|
|
ets:delete(?KV_TAB, Key).
|
|
|
|
%%--------------------------------------------------------------------
|
|
%% Date functions
|
|
%%--------------------------------------------------------------------
|
|
|
|
now_rfc3339() ->
|
|
now_rfc3339(<<"second">>).
|
|
|
|
now_rfc3339(Unit) ->
|
|
unix_ts_to_rfc3339(now_timestamp(Unit), Unit).
|
|
|
|
unix_ts_to_rfc3339(Epoch) ->
|
|
unix_ts_to_rfc3339(Epoch, <<"second">>).
|
|
|
|
unix_ts_to_rfc3339(Epoch, Unit) when is_integer(Epoch) ->
|
|
emqx_plugin_libs_rule:bin(
|
|
calendar:system_time_to_rfc3339(
|
|
Epoch, [{unit, time_unit(Unit)}])).
|
|
|
|
rfc3339_to_unix_ts(DateTime) ->
|
|
rfc3339_to_unix_ts(DateTime, <<"second">>).
|
|
|
|
rfc3339_to_unix_ts(DateTime, Unit) when is_binary(DateTime) ->
|
|
calendar:rfc3339_to_system_time(binary_to_list(DateTime),
|
|
[{unit, time_unit(Unit)}]).
|
|
|
|
now_timestamp() ->
|
|
erlang:system_time(second).
|
|
|
|
now_timestamp(Unit) ->
|
|
erlang:system_time(time_unit(Unit)).
|
|
|
|
time_unit(<<"second">>) -> second;
|
|
time_unit(<<"millisecond">>) -> millisecond;
|
|
time_unit(<<"microsecond">>) -> microsecond;
|
|
time_unit(<<"nanosecond">>) -> nanosecond.
|
|
|
|
%% @doc This is for sql funcs that should be handled in the specific modules.
|
|
%% Here the emqx_rule_funcs module acts as a proxy, forwarding
|
|
%% the function handling to the worker module.
|
|
%% @end
|
|
% '$handle_undefined_function'(schema_decode, [SchemaId, Data|MoreArgs]) ->
|
|
% emqx_schema_parser:decode(SchemaId, Data, MoreArgs);
|
|
% '$handle_undefined_function'(schema_decode, Args) ->
|
|
% error({args_count_error, {schema_decode, Args}});
|
|
|
|
% '$handle_undefined_function'(schema_encode, [SchemaId, Term|MoreArgs]) ->
|
|
% emqx_schema_parser:encode(SchemaId, Term, MoreArgs);
|
|
% '$handle_undefined_function'(schema_encode, Args) ->
|
|
% error({args_count_error, {schema_encode, Args}});
|
|
|
|
% '$handle_undefined_function'(sprintf, [Format|Args]) ->
|
|
% erlang:apply(fun sprintf_s/2, [Format, Args]);
|
|
|
|
% '$handle_undefined_function'(Fun, Args) ->
|
|
% error({sql_function_not_supported, function_literal(Fun, Args)}).
|
|
|
|
'$handle_undefined_function'(sprintf, [Format|Args]) ->
|
|
erlang:apply(fun sprintf_s/2, [Format, Args]);
|
|
|
|
'$handle_undefined_function'(Fun, Args) ->
|
|
error({sql_function_not_supported, function_literal(Fun, Args)}).
|
|
|
|
map_path(Key) ->
|
|
{path, [{key, P} || P <- string:split(Key, ".", all)]}.
|
|
|
|
function_literal(Fun, []) when is_atom(Fun) ->
|
|
atom_to_list(Fun) ++ "()";
|
|
function_literal(Fun, [FArg | Args]) when is_atom(Fun), is_list(Args) ->
|
|
WithFirstArg = io_lib:format("~ts(~0p", [atom_to_list(Fun), FArg]),
|
|
lists:foldl(fun(Arg, Literal) ->
|
|
io_lib:format("~ts, ~0p", [Literal, Arg])
|
|
end, WithFirstArg, Args) ++ ")";
|
|
function_literal(Fun, Args) ->
|
|
{invalid_func, {Fun, Args}}.
|