feat(variform): Add more functions
This commit is contained in:
parent
b76b6fbe63
commit
f80d078de3
|
@ -202,7 +202,8 @@
|
||||||
-export([
|
-export([
|
||||||
md5/1,
|
md5/1,
|
||||||
sha/1,
|
sha/1,
|
||||||
sha256/1
|
sha256/1,
|
||||||
|
hash/2
|
||||||
]).
|
]).
|
||||||
|
|
||||||
%% zip Funcs
|
%% zip Funcs
|
||||||
|
@ -710,24 +711,11 @@ map(Map = #{}) ->
|
||||||
map(Data) ->
|
map(Data) ->
|
||||||
error(badarg, [Data]).
|
error(badarg, [Data]).
|
||||||
|
|
||||||
bin2hexstr(Bin) when is_binary(Bin) ->
|
bin2hexstr(Bin) ->
|
||||||
emqx_utils:bin_to_hexstr(Bin, upper);
|
emqx_variform_bif:bin2hexstr(Bin).
|
||||||
%% If Bin is a bitstring which is not divisible by 8, we pad it and then do the
|
|
||||||
%% conversion
|
|
||||||
bin2hexstr(Bin) when is_bitstring(Bin), (8 - (bit_size(Bin) rem 8)) >= 4 ->
|
|
||||||
PadSize = 8 - (bit_size(Bin) rem 8),
|
|
||||||
Padding = <<0:PadSize>>,
|
|
||||||
BinToConvert = <<Padding/bitstring, Bin/bitstring>>,
|
|
||||||
<<_FirstByte:8, HexStr/binary>> = emqx_utils:bin_to_hexstr(BinToConvert, upper),
|
|
||||||
HexStr;
|
|
||||||
bin2hexstr(Bin) when is_bitstring(Bin) ->
|
|
||||||
PadSize = 8 - (bit_size(Bin) rem 8),
|
|
||||||
Padding = <<0:PadSize>>,
|
|
||||||
BinToConvert = <<Padding/bitstring, Bin/bitstring>>,
|
|
||||||
emqx_utils:bin_to_hexstr(BinToConvert, upper).
|
|
||||||
|
|
||||||
hexstr2bin(Str) when is_binary(Str) ->
|
hexstr2bin(Str) ->
|
||||||
emqx_utils:hexstr_to_bin(Str).
|
emqx_variform_bif:hexstr2bin(Str).
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% NULL Funcs
|
%% NULL Funcs
|
||||||
|
@ -1001,7 +989,7 @@ sha256(S) when is_binary(S) ->
|
||||||
hash(sha256, S).
|
hash(sha256, S).
|
||||||
|
|
||||||
hash(Type, Data) ->
|
hash(Type, Data) ->
|
||||||
emqx_utils:bin_to_hexstr(crypto:hash(Type, Data), lower).
|
emqx_variform_bif:hash(Type, Data).
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% gzip Funcs
|
%% gzip Funcs
|
||||||
|
|
|
@ -61,6 +61,21 @@
|
||||||
%% Control functions
|
%% Control functions
|
||||||
-export([coalesce/1, coalesce/2]).
|
-export([coalesce/1, coalesce/2]).
|
||||||
|
|
||||||
|
%% Random functions
|
||||||
|
-export([rand_str/1, rand_int/1]).
|
||||||
|
|
||||||
|
%% Schema-less encod/decode
|
||||||
|
-export([
|
||||||
|
bin2hexstr/1,
|
||||||
|
hexstr2bin/1,
|
||||||
|
int2hexstr/1,
|
||||||
|
base64_encode/1,
|
||||||
|
base64_decode/1
|
||||||
|
]).
|
||||||
|
|
||||||
|
%% Hash functions
|
||||||
|
-export([hash/2, hash_to_range/3, map_to_range/3]).
|
||||||
|
|
||||||
-define(IS_EMPTY(X), (X =:= <<>> orelse X =:= "" orelse X =:= undefined)).
|
-define(IS_EMPTY(X), (X =:= <<>> orelse X =:= "" orelse X =:= undefined)).
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
@ -389,3 +404,122 @@ is_hex_digit(_) -> false.
|
||||||
|
|
||||||
any_to_str(Data) ->
|
any_to_str(Data) ->
|
||||||
emqx_utils_conv:bin(Data).
|
emqx_utils_conv:bin(Data).
|
||||||
|
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
%% Random functions
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
%% @doc Make a random string with urlsafe-base64 charset.
|
||||||
|
rand_str(Length) when is_integer(Length) andalso Length > 0 ->
|
||||||
|
RawBytes = erlang:ceil((Length * 3) / 4),
|
||||||
|
RandomData = rand:bytes(RawBytes),
|
||||||
|
urlsafe(binary:part(base64_encode(RandomData), 0, Length));
|
||||||
|
rand_str(_) ->
|
||||||
|
throw(#{reason => badarg, function => ?FUNCTION_NAME}).
|
||||||
|
|
||||||
|
%% @doc Make a random integer in the range `[1, N]`.
|
||||||
|
rand_int(N) when is_integer(N) andalso N >= 1 ->
|
||||||
|
rand:uniform(N);
|
||||||
|
rand_int(N) ->
|
||||||
|
throw(#{reason => badarg, function => ?FUNCTION_NAME, expected => "positive integer", got => N}).
|
||||||
|
|
||||||
|
%% TODO: call base64:encode(Bin, #{mode => urlsafe, padding => false})
|
||||||
|
%% when oldest OTP to support is 26 or newer.
|
||||||
|
urlsafe(Str0) ->
|
||||||
|
Str = replace(Str0, <<"+">>, <<"-">>),
|
||||||
|
replace(Str, <<"/">>, <<"_">>).
|
||||||
|
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
%% Data encoding
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
%% @doc Encode an integer to hex string. e.g. 15 as 'f'
|
||||||
|
int2hexstr(Int) ->
|
||||||
|
erlang:integer_to_binary(Int, 16).
|
||||||
|
|
||||||
|
%% @doc Encode bytes in hex string format.
|
||||||
|
bin2hexstr(Bin) when is_binary(Bin) ->
|
||||||
|
emqx_utils:bin_to_hexstr(Bin, upper);
|
||||||
|
%% If Bin is a bitstring which is not divisible by 8, we pad it and then do the
|
||||||
|
%% conversion
|
||||||
|
bin2hexstr(Bin) when is_bitstring(Bin), (8 - (bit_size(Bin) rem 8)) >= 4 ->
|
||||||
|
PadSize = 8 - (bit_size(Bin) rem 8),
|
||||||
|
Padding = <<0:PadSize>>,
|
||||||
|
BinToConvert = <<Padding/bitstring, Bin/bitstring>>,
|
||||||
|
<<_FirstByte:8, HexStr/binary>> = emqx_utils:bin_to_hexstr(BinToConvert, upper),
|
||||||
|
HexStr;
|
||||||
|
bin2hexstr(Bin) when is_bitstring(Bin) ->
|
||||||
|
PadSize = 8 - (bit_size(Bin) rem 8),
|
||||||
|
Padding = <<0:PadSize>>,
|
||||||
|
BinToConvert = <<Padding/bitstring, Bin/bitstring>>,
|
||||||
|
emqx_utils:bin_to_hexstr(BinToConvert, upper).
|
||||||
|
|
||||||
|
%% @doc Decode hex string into its original bytes.
|
||||||
|
hexstr2bin(Str) when is_binary(Str) ->
|
||||||
|
emqx_utils:hexstr_to_bin(Str).
|
||||||
|
|
||||||
|
%% @doc Encode any bytes to base64.
|
||||||
|
base64_encode(Bin) ->
|
||||||
|
base64:encode(Bin).
|
||||||
|
|
||||||
|
%% @doc Decode base64 encoded string.
|
||||||
|
base64_decode(Bin) ->
|
||||||
|
base64:decode(Bin).
|
||||||
|
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
%% Hash functions
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
%% @doc Hash with all available algorithm provided by crypto module.
|
||||||
|
%% Return hex format string.
|
||||||
|
%% - md4 | md5
|
||||||
|
%% - sha (sha1)
|
||||||
|
%% - sha224 | sha256 | sha384 | sha512
|
||||||
|
%% - sha3_224 | sha3_256 | sha3_384 | sha3_512
|
||||||
|
%% - shake128 | shake256
|
||||||
|
%% - blake2b | blake2s
|
||||||
|
hash(<<"sha1">>, Bin) ->
|
||||||
|
hash(sha, Bin);
|
||||||
|
hash(Algorithm, Bin) when is_binary(Algorithm) ->
|
||||||
|
Type =
|
||||||
|
try
|
||||||
|
binary_to_existing_atom(Algorithm)
|
||||||
|
catch
|
||||||
|
_:_ ->
|
||||||
|
throw(#{
|
||||||
|
reason => unknown_hash_algorithm,
|
||||||
|
algorithm => Algorithm
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
hash(Type, Bin);
|
||||||
|
hash(Type, Bin) when is_atom(Type) ->
|
||||||
|
%% lower is for backward compatibility
|
||||||
|
emqx_utils:bin_to_hexstr(crypto:hash(Type, Bin), lower).
|
||||||
|
|
||||||
|
%% @doc Hash binary data to an integer within a specified range [Min, Max]
|
||||||
|
hash_to_range(Bin, Min, Max) when
|
||||||
|
is_binary(Bin) andalso
|
||||||
|
size(Bin) > 0 andalso
|
||||||
|
is_integer(Min) andalso
|
||||||
|
is_integer(Max) andalso
|
||||||
|
Min =< Max
|
||||||
|
->
|
||||||
|
Hash = hash(sha256, Bin),
|
||||||
|
HashNum = binary_to_integer(Hash, 16),
|
||||||
|
map_to_range(HashNum, Min, Max);
|
||||||
|
hash_to_range(_, _, _) ->
|
||||||
|
throw(#{reason => badarg, function => ?FUNCTION_NAME}).
|
||||||
|
|
||||||
|
map_to_range(Bin, Min, Max) when is_binary(Bin) andalso size(Bin) > 0 ->
|
||||||
|
HashNum = binary:decode_unsigned(Bin),
|
||||||
|
map_to_range(HashNum, Min, Max);
|
||||||
|
map_to_range(Int, Min, Max) when
|
||||||
|
is_integer(Int) andalso
|
||||||
|
is_integer(Min) andalso
|
||||||
|
is_integer(Max) andalso
|
||||||
|
Min =< Max
|
||||||
|
->
|
||||||
|
Range = Max - Min + 1,
|
||||||
|
Min + (Int rem Range);
|
||||||
|
map_to_range(_, _, _) ->
|
||||||
|
throw(#{reason => badarg, function => ?FUNCTION_NAME}).
|
||||||
|
|
|
@ -57,3 +57,18 @@ regex_extract_test_() ->
|
||||||
|
|
||||||
regex_extract(Str, RegEx) ->
|
regex_extract(Str, RegEx) ->
|
||||||
emqx_variform_bif:regex_extract(Str, RegEx).
|
emqx_variform_bif:regex_extract(Str, RegEx).
|
||||||
|
|
||||||
|
rand_str_test() ->
|
||||||
|
?assertEqual(3, size(emqx_variform_bif:rand_str(3))),
|
||||||
|
?assertThrow(#{reason := badarg}, size(emqx_variform_bif:rand_str(0))).
|
||||||
|
|
||||||
|
rand_int_test() ->
|
||||||
|
N = emqx_variform_bif:rand_int(10),
|
||||||
|
?assert(N =< 10 andalso N >= 1),
|
||||||
|
?assertThrow(#{reason := badarg}, emqx_variform_bif:rand_int(0)),
|
||||||
|
?assertThrow(#{reason := badarg}, emqx_variform_bif:rand_int(-1)).
|
||||||
|
|
||||||
|
base64_encode_decode_test() ->
|
||||||
|
RandBytes = crypto:strong_rand_bytes(100),
|
||||||
|
Encoded = emqx_variform_bif:base64_encode(RandBytes),
|
||||||
|
?assertEqual(RandBytes, emqx_variform_bif:base64_decode(Encoded)).
|
||||||
|
|
|
@ -182,3 +182,39 @@ syntax_error_test_() ->
|
||||||
|
|
||||||
render(Expression, Bindings) ->
|
render(Expression, Bindings) ->
|
||||||
emqx_variform:render(Expression, Bindings).
|
emqx_variform:render(Expression, Bindings).
|
||||||
|
|
||||||
|
hash_pick_test() ->
|
||||||
|
lists:foreach(
|
||||||
|
fun(_) ->
|
||||||
|
{ok, Res} = render("nth(hash_to_range(rand_str(10),1,5),[1,2,3,4,5])", #{}),
|
||||||
|
?assert(Res >= <<"1">> andalso Res =< <<"5">>)
|
||||||
|
end,
|
||||||
|
lists:seq(1, 100)
|
||||||
|
).
|
||||||
|
|
||||||
|
map_to_range_pick_test() ->
|
||||||
|
lists:foreach(
|
||||||
|
fun(_) ->
|
||||||
|
{ok, Res} = render("nth(map_to_range(rand_str(10),1,5),[1,2,3,4,5])", #{}),
|
||||||
|
?assert(Res >= <<"1">> andalso Res =< <<"5">>)
|
||||||
|
end,
|
||||||
|
lists:seq(1, 100)
|
||||||
|
).
|
||||||
|
|
||||||
|
-define(ASSERT_BADARG(FUNC, ARGS),
|
||||||
|
?_assertEqual(
|
||||||
|
{error, #{reason => badarg, function => FUNC}},
|
||||||
|
render(atom_to_list(FUNC) ++ ARGS, #{})
|
||||||
|
)
|
||||||
|
).
|
||||||
|
|
||||||
|
to_range_badarg_test_() ->
|
||||||
|
[
|
||||||
|
?ASSERT_BADARG(hash_to_range, "(1,1,2)"),
|
||||||
|
?ASSERT_BADARG(hash_to_range, "('',1,2)"),
|
||||||
|
?ASSERT_BADARG(hash_to_range, "('a','1',2)"),
|
||||||
|
?ASSERT_BADARG(hash_to_range, "('a',2,1)"),
|
||||||
|
?ASSERT_BADARG(map_to_range, "('',1,2)"),
|
||||||
|
?ASSERT_BADARG(map_to_range, "('a','1',2)"),
|
||||||
|
?ASSERT_BADARG(map_to_range, "('a',2,1)")
|
||||||
|
].
|
||||||
|
|
Loading…
Reference in New Issue