Current section

Files

Jump to
purl src purl_composer.erl
Raw

src/purl_composer.erl

-module(purl_composer).
-feature(maybe_expr, enable).
-include("purl.hrl").
-moduledoc false.
-export([compose_uri/1]).
-spec compose_uri(Purl) -> uri_string:uri_map() when Purl :: purl:t().
compose_uri(#purl{type = Type} = Purl) ->
Specification = purl_type_registry:lookup(Type),
maps:filter(fun(_Key, Value) -> Value =/= undefined end, #{
fragment => encode_subpath(Purl#purl.subpath),
path => encode_uri_path(Purl),
query => encode_qualifiers(Purl#purl.qualifiers, Specification),
scheme => <<"pkg">>
}).
-spec encode_uri_path(Purl) -> unicode:chardata() when Purl :: purl:t().
encode_uri_path(#purl{version = undefined, type = Type, name = Name, namespace = Namespace}) ->
Segments = [Type | encode_namespace(Namespace)] ++ [encode_name(Name)],
Path = lists:join("/", Segments),
iolist_to_binary(Path);
encode_uri_path(#purl{version = Version} = Purl) ->
Path = [
encode_uri_path(Purl#purl{version = undefined}),
"@",
encode_version(Version)
],
iolist_to_binary(Path).
-spec encode_namespace(Namespace) -> [unicode:chardata()] when Namespace :: purl:namespace().
encode_namespace(Namespace) -> lists:map(fun encode/1, Namespace).
-spec encode_name(Name) -> unicode:chardata() when Name :: purl:name().
encode_name(Name) -> encode(Name, "/").
-spec encode_qualifiers(Qualifiers, Specification) -> unicode:chardata() | undefined when
Qualifiers :: purl:qualifiers(),
Specification :: purl:type_specification().
encode_qualifiers(Qualifiers, Specification) ->
DefaultQualifiers = purl_type_normalizer:default_qualifiers(Specification),
FilteredQualifiers = maps:filter(
fun(Key, Value) ->
maps:get(Key, DefaultQualifiers, undefined) =/= Value
end,
Qualifiers
),
case FilteredQualifiers =:= #{} of
true ->
undefined;
false ->
Encoded = lists:map(
fun({Key, Value}) ->
[Key, "=", encode(Value, "&+/,")]
end,
maps:to_list(FilteredQualifiers)
),
Joined = lists:join("&", Encoded),
iolist_to_binary(Joined)
end.
-spec encode_version(Version) -> unicode:chardata() when Version :: unicode:chardata().
encode_version(Version) -> encode(Version, "/").
-spec encode_subpath(Subpath) -> unicode:chardata() | undefined when Subpath :: purl:subpath().
encode_subpath([]) ->
undefined;
encode_subpath(Subpath) ->
Encoded = lists:map(fun encode/1, Subpath),
Joined = lists:join("/", Encoded),
iolist_to_binary(Joined).
-spec encode(Data) -> unicode:chardata() when Data :: unicode:chardata().
encode(Data) -> encode(Data, []).
-spec encode(Data, AdditionalCodepoints) -> unicode:chardata() when
Data :: unicode:chardata(), AdditionalCodepoints :: unicode:charlist().
encode(Data, AdditionalCodepoints) ->
AllowedCharacters = uri_string:allowed_characters(),
{reserved, Reserved} = lists:keyfind(reserved, 1, AllowedCharacters),
{unreserved, Unreserved} = lists:keyfind(unreserved, 1, AllowedCharacters),
PurlUnsafe = "#?@[]+",
UnescapedCharacters = ((Reserved ++ Unreserved) -- PurlUnsafe) -- AdditionalCodepoints,
uri_string:quote(Data, UnescapedCharacters).