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