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 all commits
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
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ true
{publisher_restrictions,0,[]},
undefined,undefined,
{consent_segment,3,...}}}

%% AC string

1> additional_consent_string:parse(<<"2~1.35.41.101~dv.9.21.81">>).
{ok, {addtl_consent,2,[1,35,41,101],[9,21,81]}}
```

## Tests
Expand Down
8 changes: 8 additions & 0 deletions include/consent_string.hrl
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@
publisher_tc :: undefined | consent_segment()
}).

-record(addtl_consent, {
version :: pos_integer(),
atp_ids :: [pos_integer()],
%% Only version 2 has this field potentially set
disclosed_atp_ids :: undefined | [pos_integer()]
}).

-record(consent_segment_entry_disclosed_vendors, {
max_vendor_id :: pos_integer(),
entries :: range_or_bitfield()
Expand Down Expand Up @@ -98,6 +105,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
96 changes: 96 additions & 0 deletions src/additional_consent_string.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
-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) ->
try
{ok, Version, <<?KNOWN_SEPARATOR, Bin1/binary>>} = parse_version(Bin),
case Version of
1 ->
{ok, AtpIds, <<"">>} = parse_atp_ids(Bin1),
{ok, #addtl_consent {
version = Version,
atp_ids = AtpIds
}};
2 ->
{ok, AtpIds, <<?KNOWN_SEPARATOR, "dv.", Bin2/binary>>} = parse_atp_ids(Bin1),
{ok, DisclosedAtpIds, <<"">>} = parse_atp_ids(Bin2),
{ok, #addtl_consent {
version = Version,
atp_ids = AtpIds,
disclosed_atp_ids = DisclosedAtpIds
}}
end
catch
_:_ ->
{error, invalid_ac_string}
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>>);
parse_version(Rest, Acc) ->
case safe_binary_to_integer(Acc) of
undefined ->
{error, invalid_ac_string};
eof ->
{error, invalid_ac_string};
Version ->
{ok, Version, Rest}
end.

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};
eof ->
{error, invalid_ac_string};
Id ->
parse_atp_ids(Rest, <<"">>, [Id | Ids])
end;
parse_atp_ids(<<Rest/binary>>, Acc, Ids) ->
case safe_binary_to_integer(Acc) of
undefined -> {error, invalid_ac_string};
eof -> {ok, lists:reverse(Ids), Rest};
Id -> {ok, lists:reverse([Id | Ids]), Rest}
end.


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

74 changes: 74 additions & 0 deletions test/ac_string_tests.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
-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)))).
-define(_assertErrorParse(A),
?_assertMatch({error, invalid_ac_string}, parse((A)))).
-define(_assertMatchParse(A, B),
?_assertMatch({ok, (A)}, parse((B)))).

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([]))
].

parse_test_() ->
[
% Version 1 tests
?_assertMatchParse(#addtl_consent{version = 1, atp_ids = []}, <<"1~">>),
?_assertMatchParse(#addtl_consent{version = 1, atp_ids = [1]}, <<"1~1">>),
?_assertMatchParse(#addtl_consent{version = 1, atp_ids = [1, 3, 2]}, <<"1~1.3.2">>),

% Version 2 tests
?_assertMatchParse(#addtl_consent{version = 2, atp_ids = [], disclosed_atp_ids = [1, 3, 2]}, <<"2~~dv.1.3.2">>),
?_assertMatchParse(#addtl_consent{version = 2, atp_ids = [], disclosed_atp_ids = []}, <<"2~~dv.">>),

% Test errors
?_assertErrorParse(<<"1~~2">>),
?_assertErrorParse(<<"1~~">>),
?_assertErrorParse(<<"1~a">>),
?_assertErrorParse(<<"1a1~">>),
% Alphanumeric ATP ids are not allowed
?_assertErrorParse(<<"1~1.1.a">>),
% Trailing ATP separator is not allowed
?_assertErrorParse(<<"1~1.">>),
% Invalid separator
?_assertErrorParse(<<"1~1,2,3">>),
% Version 1 doesn't support disclosed list of atps
?_assertErrorParse(<<"1~1~dv.">>),
% Invalid version 2 separator
?_assertErrorParse(<<"2~~a">>),
% Missing dot following version 2 separator
?_assertErrorParse(<<"2~~dv">>),
% Missing dot following version 2 separator
?_assertErrorParse(<<"2~~dv1.1.1">>)
].

%% 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])
).