%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2008-2017. 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.
%%
%% %CopyrightEnd%
%%
-module(uri_string_recompose).
-compile(export_all).
-proptest(eqc).
-proptest([triq,proper]).
-ifndef(EQC).
-ifndef(PROPER).
-ifndef(TRIQ).
-define(EQC,true).
-endif.
-endif.
-endif.
-ifdef(EQC).
-include_lib("eqc/include/eqc.hrl").
-define(MOD_eqc,eqc).
-else.
-ifdef(PROPER).
-include_lib("proper/include/proper.hrl").
-define(MOD_eqc,proper).
-else.
-ifdef(TRIQ).
-define(MOD_eqc,triq).
-include_lib("triq/include/triq.hrl").
-endif.
-endif.
-endif.
-define(STRING_REST(MatchStr, Rest), <<MatchStr/utf8, Rest/binary>>).
-define(SCHEME, {scheme, scheme()}).
-define(USER, {userinfo, unicode()}).
-define(HOST, {host, host_map()}).
-define(PORT, {port, port()}).
-define(PATH_ABE, {path, path_abempty_map()}).
-define(PATH_ABS, {path, path_absolute_map()}).
-define(PATH_NOS, {path, path_noscheme_map()}).
-define(PATH_ROO, {path, path_rootless_map()}).
-define(PATH_EMP, {path, path_empty_map()}).
-define(QUERY, {query, query_map()}).
-define(FRAGMENT, {fragment, fragment_map()}).
%%%========================================================================
%%% Properties
%%%========================================================================
prop_recompose() ->
?FORALL(Map, map(),
Map =:= uri_string:parse(uri_string:recompose(Map))
).
%% Stats
prop_map_key_length_collect() ->
?FORALL(List, map(),
collect(length(maps:keys(List)), true)).
prop_map_collect() ->
?FORALL(List, map(),
collect(lists:sort(maps:keys(List)), true)).
prop_scheme_collect() ->
?FORALL(List, scheme(),
collect(length(List), true)).
%%%========================================================================
%%% Generators
%%%========================================================================
map() ->
?LET(Gen, comp_proplist(), proplist_to_map(Gen)).
comp_proplist() ->
frequency([
{2, [?SCHEME,?PATH_ABS]},
{2, [?SCHEME,?PATH_ROO]},
{2, [?SCHEME,?PATH_EMP]},
{2, [?SCHEME,?HOST,?PATH_ABE]},
{2, [?SCHEME,?USER,?HOST,?PATH_ABE]},
{2, [?SCHEME,?HOST,?PORT,?PATH_ABE]},
{2, [?SCHEME,?USER,?HOST,?PORT,?PATH_ABE]},
{2, [?PATH_ABS]},
{2, [?PATH_NOS]},
{2, [?PATH_EMP]},
{2, [?HOST,?PATH_ABE]},
{2, [?USER,?HOST,?PATH_ABE]},
{2, [?HOST,?PORT,?PATH_ABE]},
{2, [?USER,?HOST,?PORT,?PATH_ABE]},
{2, [?SCHEME,?PATH_ABS,?QUERY]},
{2, [?SCHEME,?PATH_ROO,?QUERY]},
{2, [?SCHEME,?PATH_EMP,?QUERY]},
{2, [?SCHEME,?HOST,?PATH_ABE,?QUERY]},
{2, [?SCHEME,?USER,?HOST,?PATH_ABE,?QUERY]},
{2, [?SCHEME,?HOST,?PORT,?PATH_ABE,?QUERY]},
{2, [?SCHEME,?USER,?HOST,?PORT,?PATH_ABE,?QUERY]},
{2, [?PATH_ABS,?QUERY]},
{2, [?PATH_NOS,?QUERY]},
{2, [?PATH_EMP,?QUERY]},
{2, [?HOST,?PATH_ABE,?QUERY]},
{2, [?USER,?HOST,?PATH_ABE,?QUERY]},
{2, [?HOST,?PORT,?PATH_ABE,?QUERY]},
{2, [?USER,?HOST,?PORT,?PATH_ABE,?QUERY]},
{2, [?SCHEME,?PATH_ABS,?FRAGMENT]},
{2, [?SCHEME,?PATH_ROO,?FRAGMENT]},
{2, [?SCHEME,?PATH_EMP,?FRAGMENT]},
{2, [?SCHEME,?HOST,?PATH_ABE,?FRAGMENT]},
{2, [?SCHEME,?USER,?HOST,?PATH_ABE,?FRAGMENT]},
{2, [?SCHEME,?HOST,?PORT,?PATH_ABE,?FRAGMENT]},
{2, [?SCHEME,?USER,?HOST,?PORT,?PATH_ABE,?FRAGMENT]},
{2, [?PATH_ABS,?FRAGMENT]},
{2, [?PATH_NOS,?FRAGMENT]},
{2, [?PATH_EMP,?FRAGMENT]},
{2, [?HOST,?PATH_ABE,?FRAGMENT]},
{2, [?USER,?HOST,?PATH_ABE,?FRAGMENT]},
{2, [?HOST,?PORT,?PATH_ABE,?FRAGMENT]},
{2, [?USER,?HOST,?PORT,?PATH_ABE,?FRAGMENT]},
{2, [?SCHEME,?PATH_ABS,?QUERY,?FRAGMENT]},
{2, [?SCHEME,?PATH_ROO,?QUERY,?FRAGMENT]},
{2, [?SCHEME,?PATH_EMP,?QUERY,?FRAGMENT]},
{2, [?SCHEME,?HOST,?PATH_ABE,?QUERY,?FRAGMENT]},
{2, [?SCHEME,?USER,?HOST,?PATH_ABE,?QUERY,?FRAGMENT]},
{2, [?SCHEME,?HOST,?PORT,?PATH_ABE,?QUERY,?FRAGMENT]},
{2, [?SCHEME,?USER,?HOST,?PORT,?PATH_ABE,?QUERY,?FRAGMENT]},
{2, [?PATH_ABS,?QUERY,?FRAGMENT]},
{2, [?PATH_NOS,?QUERY,?FRAGMENT]},
{2, [?PATH_EMP,?QUERY,?FRAGMENT]},
{2, [?HOST,?PATH_ABE,?QUERY,?FRAGMENT]},
{2, [?USER,?HOST,?PATH_ABE,?QUERY,?FRAGMENT]},
{2, [?HOST,?PORT,?PATH_ABE,?QUERY,?FRAGMENT]},
{2, [?USER,?HOST,?PORT,?PATH_ABE,?QUERY,?FRAGMENT]}
]).
%%-------------------------------------------------------------------------
%% Path
%%-------------------------------------------------------------------------
path_abempty_map() ->
frequency([{90, path_abe_map()},
{10, path_empty_map()}]).
path_abe_map() ->
?SIZED(Length, path_abe_map(Length, [])).
%%
path_abe_map(0, Segments) ->
?LET(Gen, Segments, lists:append(Gen));
path_abe_map(N, Segments) ->
path_abe_map(N-1, [slash(),segment()|Segments]).
path_absolute_map() ->
?SIZED(Length, path_absolute_map(Length, [])).
%%
path_absolute_map(0, Segments) ->
?LET(Gen, [slash(),segment_nz()|Segments], lists:append(Gen));
path_absolute_map(N, Segments) ->
path_absolute_map(N-1, [slash(),segment()|Segments]).
path_noscheme_map() ->
?SIZED(Length, path_noscheme_map(Length, [])).
%%
path_noscheme_map(0, Segments) ->
?LET(Gen, [segment_nz_nc()|Segments], lists:append(Gen));
path_noscheme_map(N, Segments) ->
path_noscheme_map(N-1, [slash(),segment()|Segments]).
path_rootless_map() ->
?SIZED(Length, path_rootless_map(Length, [])).
%%
path_rootless_map(0, Segments) ->
?LET(Gen, [segment_nz()|Segments], lists:append(Gen));
path_rootless_map(N, Segments) ->
path_rootless_map(N-1, [slash(),segment()|Segments]).
segment_nz() ->
non_empty(segment()).
segment_nz_nc() ->
non_empty(list(frequency([{30, unreserved()},
{10, sub_delims()},
{10, unicode_char()},
{5, oneof([$@])}
]))).
segment() ->
list(frequency([{30, unreserved()},
{10, sub_delims()},
{10, unicode_char()},
{5, oneof([$:, $@])}
])).
slash() ->
"/".
path_empty_map() ->
"".
%%-------------------------------------------------------------------------
%% Path
%%-------------------------------------------------------------------------
host_map() ->
frequency([{30, reg_name()},
{30, ip_address()}
]).
reg_name() ->
list(frequency([{30, alpha()},
{10, sub_delims()},
{10, unicode_char()}
])).
ip_address() ->
oneof(["127.0.0.1", "::127.0.0.1",
"2001:0db8:0000:0000:0000:0000:1428:07ab",
"2001:0db8:0000:0000:0000::1428:07ab",
"2001:0db8:0:0:0:0:1428:07ab",
"2001:0db8:0::0:1428:07ab"]).
%% Generating only reg-names
host_uri() ->
non_empty(list(frequency([{30, unreserved()},
{10, sub_delims()},
{10, pct_encoded()}
]))).
%%-------------------------------------------------------------------------
%% Port, Query, Fragment
%%-------------------------------------------------------------------------
port() ->
frequency([{10, undefined},
{10, range(1,65535)}
]).
query_map() ->
unicode().
query_uri() ->
[$?| non_empty(list(frequency([{20, pchar()},
{5, oneof([$/, $?])} % punctuation
])))].
fragment_map() ->
unicode().
fragment_uri() ->
[$?| non_empty(list(frequency([{20, pchar()},
{5, oneof([$/, $?])} % punctuation
])))].
%%-------------------------------------------------------------------------
%% Scheme
%%-------------------------------------------------------------------------
scheme() ->
?SIZED(Length, scheme_start(Length, [])).
%%
scheme_start(0, L) ->
?LET(Gen, L, lists:reverse(Gen));
scheme_start(N, L) ->
scheme(N-1,[alpha()|L]).
scheme(0, L) ->
?LET(Gen, L, lists:reverse(Gen));
scheme(N, L) ->
scheme(N-1, [scheme_char()|L]).
%%-------------------------------------------------------------------------
%% Misc
%%-------------------------------------------------------------------------
unicode() ->
list(frequency([{20, alpha()}, % alpha
{10, digit()}, % digit
{10, unicode_char()} % unicode
])).
scheme_char() ->
frequency([{20, alpha()}, % alpha
{20, digit()}, % digit
{5, oneof([$+, $-, $.])} % punctuation
]).
sub_delims() ->
oneof([$!, $$, $&, $', $(, $),
$*, $+, $,,$;, $=]).
pchar() ->
frequency([{20, unreserved()},
{5, pct_encoded()},
{5, sub_delims()},
{1, oneof([$:, $@])} % punctuation
]).
unreserved() ->
frequency([{20, alpha()},
{5, digit()},
{1, oneof([$-, $., $_, $~])} % punctuation
]).
unicode_char() ->
range(913, 1023).
alpha() ->
frequency([{20, range($a, $z)}, % letters
{20, range($A, $Z)}]). % letters
digit() ->
range($0, $9). % numbers
pct_encoded() ->
oneof(["%C3%A4", "%C3%A5", "%C3%B6"]).
%%%========================================================================
%%% Helpers
%%%========================================================================
proplist_to_map(L) ->
lists:foldl(fun({K,V},M) -> M#{K => V};
(_,M) -> M
end, #{}, L).