feat(variform): Add more functions
This commit is contained in:
parent
b76b6fbe63
commit
f80d078de3
|
@ -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
|
||||
|
|
|
@ -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}).
|
||||
|
|
|
@ -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)).
|
||||
|
|
|
@ -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)")
|
||||
].
|
||||
|
|
Loading…
Reference in New Issue