Packages

Enhanced query string parser

Current section

Files

Jump to
qsp src qsp.erl
Raw

src/qsp.erl

-module(qsp).
-export([decode/1]).
%%
%% Decode
%%
decode(QS) ->
PL = cow_qs:parse_qs(QS),
lists:foldr(fun decode_pair/2, #{}, PL).
decode_pair({Key, Value}, Acc) ->
BL = binary:last(Key),
Parts = if
Key /= <<"">> andalso BL == $] ->
Subkey = binary:part(Key, 0, byte_size(Key) - 1),
case binary:split(Subkey, <<"[">>) of
[Key2, Subpart] ->
[Key2 | binary:split(Subpart, <<"][">>, [global])];
_ ->
[Key]
end;
true ->
[Key]
end, assign_parts(Parts, Value, Acc).
assign_parts([Key], Value, Acc) ->
case maps:find(Key, Acc) of
{ok, _Value} -> Acc;
_ -> maps:put(Key, Value, Acc)
end;
assign_parts([Key, <<"">> | T], Value, Acc) ->
case maps:find(Key, Acc) of
{ok, Current} when is_list(Current) ->
maps:put(Key, assign_list(T, Current, Value), Acc);
error ->
maps:put(Key, assign_list(T, [], Value), Acc);
_ ->
Acc
end;
assign_parts([Key | T], Value, Acc) ->
case maps:find(Key, Acc) of
{ok, Current} ->
maps:put(Key, assign_parts(T, Value, Current), Acc);
error ->
maps:put(Key, assign_parts(T, Value, #{}), Acc);
_ ->
Acc
end.
assign_list(T, Current, Value) -> [assign_list(T, Value) | Current].
assign_list([], Value) -> Value;
assign_list(T, Value) -> assign_parts(T, Value, #{}).
%%
%% Test
%%
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
-define(asrt(A, B), ?assertEqual(B, A)).
get(K, M) ->
case maps:find(K, M) of
{ok, V} -> V;
U -> U
end.
benchmark(Tries, M, F, A) ->
Total = lists:foldl(fun(_, Acc) ->
{Time, _} = timer:tc(M, F, A),
Acc + Time
end, 0, lists:seq(0, Tries)),
?debugFmt("benchmark ~s:~s - ~pmcs~n", [M, F, Total / Tries]).
decode_benchmark_test() ->
QS = <<"foo=bar&baz=bat&x[y][z][]=1&x[y][z][]=2&my+weird+field=q1%212%22">>,
benchmark(100000, qsp, decode, [QS]).
decode_simple_test() ->
P = decode(<<"foo=bar&baz=bat">>),
?asrt(get(<<"foo">>, P),
<<"bar">>),
?asrt(get(<<"baz">>, P),
<<"bat">>).
decode_hash_test() ->
P = decode(<<"users[name]=hello&users[age]=17">>),
?asrt(get(<<"users">>, P),
#{<<"name">> => <<"hello">>, <<"age">> => <<"17">>}).
decode_simple_list_test() ->
?asrt(decode(<<"foo[]">>),
#{<<"foo">> => [true]}),
?asrt(decode(<<"foo[]=">>),
#{<<"foo">> => [<<>>]}),
?asrt(decode(<<"foo[]=bar&foo[]=baz">>),
#{<<"foo">> => [<<"bar">>, <<"baz">>]}).
decode_list_test() ->
P = decode(<<"foo[]=bar&foo[]=baz&bat[]=1&bat[]=2">>),
?asrt(get(<<"foo">>, P),
[<<"bar">>, <<"baz">>]),
?asrt(get(<<"bat">>, P),
[<<"1">>, <<"2">>]).
decode_nested_test() ->
?asrt(decode(<<"x[y][z]=1">>),
#{<<"x">> => #{<<"y">> => #{<<"z">> => <<"1">>}}}),
?asrt(decode(<<"x[y][z][]=1">>),
#{<<"x">> => #{<<"y">> => #{<<"z">> => [<<"1">>]}}}),
?asrt(decode(<<"x[y][z][]=1&x[y][z][]=2">>),
#{<<"x">> => #{<<"y">> => #{<<"z">> => [<<"1">>, <<"2">>]}}}),
?asrt(decode(<<"x[y][][z]=1">>),
#{<<"x">> => #{<<"y">> => [#{<<"z">> => <<"1">>}]}}),
?asrt(decode(<<"x[y][][z][]=1">>),
#{<<"x">> => #{<<"y">> => [#{<<"z">> => [<<"1">>]}]}}).
decode_weird_query_test() ->
P = decode(<<"my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F">>),
?asrt(get(<<"my weird field">>, P),
<<"q1!2\"'w$5&7/z8)?">>).
decode_weird_kv_test() ->
?asrt(decode(<<"key=">>),
#{<<"key">> => <<>>}).
decode_accept_last_test() ->
?asrt(decode(<<"x[]=1&x[y]=1">>),
#{<<"x">> => #{<<"y">> => <<"1">>}}),
?asrt(decode(<<"x[y][][w]=2&x[y]=1">>),
#{<<"x">> => #{<<"y">> => <<"1">>}}),
?asrt(decode(<<"x=1&x[y]=1">>),
#{<<"x">> => #{<<"y">> => <<"1">>}}).
-endif.