feat(variform): Add more functions

This commit is contained in:
zmstone 2024-04-14 15:17:43 +02:00
parent b76b6fbe63
commit f80d078de3
4 changed files with 192 additions and 19 deletions

View File

@ -202,7 +202,8 @@
-export([
md5/1,
sha/1,
sha256/1
sha256/1,
hash/2
]).
%% zip Funcs
@ -710,24 +711,11 @@ map(Map = #{}) ->
map(Data) ->
error(badarg, [Data]).
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).
bin2hexstr(Bin) ->
emqx_variform_bif:bin2hexstr(Bin).
hexstr2bin(Str) when is_binary(Str) ->
emqx_utils:hexstr_to_bin(Str).
hexstr2bin(Str) ->
emqx_variform_bif:hexstr2bin(Str).
%%------------------------------------------------------------------------------
%% NULL Funcs
@ -1001,7 +989,7 @@ sha256(S) when is_binary(S) ->
hash(sha256, S).
hash(Type, Data) ->
emqx_utils:bin_to_hexstr(crypto:hash(Type, Data), lower).
emqx_variform_bif:hash(Type, Data).
%%------------------------------------------------------------------------------
%% gzip Funcs

View File

@ -61,6 +61,21 @@
%% Control functions
-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)).
%%------------------------------------------------------------------------------
@ -389,3 +404,122 @@ is_hex_digit(_) -> false.
any_to_str(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}).

View File

@ -57,3 +57,18 @@ regex_extract_test_() ->
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)).

View File

@ -182,3 +182,39 @@ syntax_error_test_() ->
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)")
].