Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement additional consent string parsing #13

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions include/consent_string.hrl
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@
publisher_tc :: undefined | consent_segment()
}).

-record(addtl_consent, {
version :: pos_integer(),
atp_ids :: [pos_integer()]
}).

-record(consent_segment_entry_disclosed_vendors, {
max_vendor_id :: pos_integer(),
entries :: range_or_bitfield()
Expand Down Expand Up @@ -98,6 +103,7 @@
}).

-type consent() :: #consent {}.
-type addtl_consent() :: #addtl_consent {}.

-type consent_segment() :: #consent_segment {}.

Expand Down
2 changes: 0 additions & 2 deletions rebar.config
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@
]}
]}.

{project_plugins, [rebar3_lint]}.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removes duplicate line added in #14


{xref_checks, [
deprecated_functions,
deprecated_function_calls,
Expand Down
89 changes: 89 additions & 0 deletions src/additional_consent_string.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
-module(additional_consent_string).
-include("consent_string.hrl").

-compile([inline]).

-export([parse/1]).
-ifdef(TEST).
-export([parse_version/1, parse_atp_ids/1]).
-endif.

-define(KNOWN_SEPARATOR, "~").


-spec parse(binary()) ->
{ok, addtl_consent()} | {error, invalid_ac_string}.

% @doc Parses an additional consent string, returning the version
% number and the list of numerical ATP ids.
%
% @returns `{ok, #addtl_consent{}}' if the string is valid,
% otherwise an appropriate error is returned.
%
parse(Bin) when is_binary(Bin) ->
case parse_version(Bin) of
{ok, Version, Bin1} ->
case parse_atp_ids(Bin1) of
{ok, AtpIds} ->
{ok, #addtl_consent {
version = Version,
atp_ids = AtpIds
}};
Error -> Error
end;
Error -> Error
end;
parse(_) ->
{error, invalid_ac_string}.

parse_version(Bin) ->
parse_version(Bin, <<"">>).

parse_version(<<Bin, Rest/binary>>, Acc) when Bin >= $0, Bin =< $9 ->
parse_version(Rest, <<Acc/binary, Bin>>);
% Consume the separator token
parse_version(<<?KNOWN_SEPARATOR, Rest/binary>>, Acc) ->
case safe_binary_to_integer(Acc) of
undefined ->
{error, invalid_ac_string};
Version ->
{ok, Version, Rest}
end;
parse_version(_, _) ->
{error, invalid_ac_string}.

parse_atp_ids(Bin) ->
parse_atp_ids(Bin, <<"">>, []).

parse_atp_ids(<<Bin, Rest/binary>>, Acc, Ids) when Bin >= $0, Bin =< $9 ->
parse_atp_ids(Rest, <<Acc/binary, Bin>>, Ids);
parse_atp_ids(<<".", Rest/binary>>, Acc, Ids) when Rest =/= <<"">> ->
case safe_binary_to_integer(Acc) of
undefined ->
{error, invalid_ac_string};
Id ->
parse_atp_ids(Rest, <<"">>, [Id | Ids])
end;
parse_atp_ids(<<_Invalid, _Rest/binary>>, _Acc, _Ids) ->
{error, invalid_ac_string};
parse_atp_ids(<<"">>, <<"">>, Ids) ->
{ok, lists:reverse(Ids)};
parse_atp_ids(<<"">>, Acc, Ids) ->
case safe_binary_to_integer(Acc) of
undefined ->
{error, invalid_ac_string};
Id ->
% Preserve the order of ids
{ok, lists:reverse([Id | Ids])}
end.


-spec safe_binary_to_integer(binary()) -> undefined | integer().
safe_binary_to_integer(Bin) ->
try
binary_to_integer(Bin, 10)
catch
_:_ ->
undefined
end.

75 changes: 75 additions & 0 deletions test/ac_string_tests.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
-module(ac_string_tests).
-include("consent_string.hrl").
-include_lib("eunit/include/eunit.hrl").

-define(_assertMatchAtp(A, B),
?_assertMatch({ok, (A)}, parse_atp_ids((B)))).
-define(_assertMatchVersion(A, B),
?_assertMatch({ok, (A), _}, parse_version((B)))).
-define(_assertErrorVersion(B),
?_assertMatch({error, invalid_ac_string}, parse_version((B)))).
-define(_assertErrorAtp(B),
?_assertMatch({error, invalid_ac_string}, parse_atp_ids((B)))).

parse_version_only_test_() ->
[
?_assertMatchVersion(20, <<"20~">>),
?_assertMatchVersion(0, <<"0~">>),

% Test error clauses
% Make sure that random separators are not recognized
?_assertErrorVersion(<<"1=">>),
% Alphanumeric character in version
?_assertErrorVersion(<<"1a~">>),
% Missing separator at the end
?_assertErrorVersion(<<"1">>),
% Separator at the start
?_assertErrorVersion(<<"~1">>),
% Separator at both eds
?_assertErrorVersion(<<"~1~">>)
].

parse_atps_only_test_() ->
[
% Make sure the order is preserved after parsing
?_assertMatchAtp([1, 10, 100, 20], atps([1, 10, 100, 20])),
?_assertMatchAtp([1], atps([1])),
% Empty list of version should NOT cause an error
?_assertMatchAtp([], atps([])),

% Test error clauses
% Alphanumeric ids are not allowed
?_assertErrorAtp(<<"1.1.a">>),
% Trailing separators are not allowed
?_assertErrorAtp(<<"1.">>),
% Invalid separator
?_assertErrorAtp(<<"1,2,3">>)
].

parse_test_() ->
[
?_assertMatch({ok, #addtl_consent{version = 1, atp_ids = []}}, parse(<<"1~">>)),
?_assertMatch({ok, #addtl_consent{version = 1, atp_ids = [1]}}, parse(<<"1~1">>)),
?_assertMatch({ok, #addtl_consent{version = 1, atp_ids = [1, 3, 2]}}, parse(<<"1~1.3.2">>)),

% Test errors
?_assertMatch({error, invalid_ac_string}, parse(<<"1~a">>)),
?_assertMatch({error, invalid_ac_string}, parse(<<"1a1~">>))
].

%% Helpers
parse(Bin) ->
additional_consent_string:parse(Bin).

parse_version(Bin) ->
additional_consent_string:parse_version(Bin).

parse_atp_ids(Bin) ->
additional_consent_string:parse_atp_ids(Bin).

atps([]) ->
<<"">>;
atps(List) ->
list_to_binary(
lists:join(".", [integer_to_list(X) || X <- List])
).