Current section

Files

Jump to
purl src purl.erl
Raw

src/purl.erl

-module(purl).
-feature(maybe_expr, enable).
-include("internal/doc.hrl").
-include("purl.hrl").
?MODULEDOC("""
Erlang Implementation of the purl (package url) specification.
## Specification
https://github.com/package-url/purl-spec
**Format**: `pkg:type/namespace/name@version?qualifiers#subpath`
> #### License {: .neutral}
>
> A lot of the documentation was taken directly from the specification. It is
> licensed under the MIT License:
> ```
> Copyright (c) the purl authors
>
> Permission is hereby granted, free of charge, to any person obtaining a copy of
> this software and associated documentation files (the "Software"), to deal in
> the Software without restriction, including without limitation the rights to
> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
> the Software, and to permit persons to whom the Software is furnished to do so,
> subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in all
> copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
> FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
> COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
> IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
> CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
> ```
""").
?MODULEDOC(#{since => <<"0.3.0">>}).
-export_type([
parse_error/0,
type/0,
namespace_segment/0,
namespace/0,
name/0,
version/0,
qualifier_key/0,
qualifier_value/0,
qualifiers/0,
subpath_segment/0,
subpath/0,
t/0
]).
-export([to_binary/1, to_uri/1, new/1, from_resource_uri/1]).
?DOC(#{since => <<"0.3.0">>}).
-type parse_error() ::
{error,
{invalid_field, Field :: atom(), Value :: binary()}
| {duplicate_qualifier, Key :: binary()}
| {invalid_scheme, Scheme :: binary() | undefined}
| {special_case_failed, Message :: binary()}}
| uri_string:error().
?DOC("""
the package "type" or package "protocol" such as `maven`, `npm`, `nuget`,
`gem`, `pypi`, etc.
Known types: https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst
## Validation
* The package type is composed only of ASCII letters and numbers, '.', '+' and '-' (period, plus, and dash)
* The type cannot start with a number
* The type cannot contains spaces
* The type must NOT be percent-encoded
* The type is case insensitive. The canonical form is lowercase
""").
?DOC(#{since => <<"0.3.0">>}).
-type type() :: binary().
?DOC("""
Segment of the namespace
## Validation
* must not contain a '/'
* must not be empty
* A URL host or Authority must NOT be used as a namespace. Use instead a
`repository_url` qualifier. Note however that for some types, the namespace
may look like a host.
""").
?DOC(#{since => <<"0.3.0">>}).
-type namespace_segment() :: binary().
?DOC("""
some name prefix such as a Maven groupid, a Docker image owner, a GitHub user
or organization
The values are type-specific.
""").
?DOC(#{since => <<"0.3.0">>}).
-type namespace() :: [namespace_segment()].
?DOC("""
the name of the package
""").
?DOC(#{since => <<"0.3.0">>}).
-type name() :: binary().
?DOC("""
the version of the package
A version is a plain and opaque string. Some package types use versioning
conventions such as semver for NPMs or nevra conventions for RPMS. A type may
define a procedure to compare and sort versions, but there is no reliable and
uniform way to do such comparison consistently.
""").
?DOC(#{since => <<"0.3.0">>}).
-type version() :: binary().
?DOC("""
qualifier key
## Validation
* The key must be composed only of ASCII letters and numbers, '.', '-' and '_' (period, dash and underscore)
* A key cannot start with a number
* A key must NOT be percent-encoded
* A key is case insensitive. The canonical form is lowercase
* A key cannot contains spaces
""").
?DOC(#{since => <<"0.3.0">>}).
-type qualifier_key() :: binary().
?DOC("""
qualifier value
## Validation
* value cannot be an empty string: a key=value pair with an empty value is the
same as no key/value at all for this key
""").
?DOC(#{since => <<"0.3.0">>}).
-type qualifier_value() :: binary().
?DOC("""
extra qualifying data for a package such as an OS, architecture, a distro,
etc.
The values are type-specific.
## Validation
* key must be unique within the keys of the qualifiers string
""").
?DOC(#{since => <<"0.3.0">>}).
-type qualifiers() :: #{qualifier_key() => qualifier_value()}.
?DOC("""
subpath segment
## Validation
* must not contain a '/'
* must not be any of '..' or '.'
* must not be empty
""").
?DOC(#{since => <<"0.3.0">>}).
-type subpath_segment() :: binary().
?DOC("""
extra subpath within a package, relative to the package root
""").
?DOC(#{since => <<"0.3.0">>}).
-type subpath() :: [subpath_segment()].
?DOC("""
Package URL record
""").
?DOC(#{since => <<"0.3.0">>}).
-type t() :: #purl{
type :: purl:type(),
namespace :: purl:namespace(),
name :: purl:name(),
version :: purl:version() | undefined,
qualifiers :: purl:qualifiers(),
subpath :: purl:subpath()
}.
?DOC("""
Formats purl as binary
## Examples
```
> purl:to_binary(#purl{type = "hex", name = "purl", namespace = [], subpath = [], qualifiers = #{}})
<<"pkg:hex/purl">>
```
""").
?DOC(#{since => <<"0.3.0">>}).
-spec to_binary(Purl) -> unicode:chardata() when Purl :: t().
to_binary(#purl{} = Purl) -> uri_string:recompose(to_uri(Purl)).
?DOC("""
Converts a purl to a `uri_string:uri_map()`
## Examples
```
> purl:to_uri(#purl{type = "hex", name = "purl", namespace = [], subpath = [], qualifiers = #{}})
#{scheme=><<"pkg">>,path=><<"hex/purl">>}
```
""").
?DOC(#{since => <<"0.3.0">>}).
-spec to_uri(Purl) -> uri_string:uri_map() when Purl :: t().
to_uri(#purl{} = Purl) -> purl_composer:compose_uri(Purl).
?DOC("""
Creates a new purl struct from a `Purl`, `URI` or string.
## Examples
```
> purl:new(<<"pkg:hex/purl">>)
{ok, #purl{type = <<"hex">>, name = <<"purl">>, namespace = [], subpath = [], qualifiers = #{}}}
```
""").
?DOC(#{since => <<"0.3.0">>}).
-spec new(Purl) -> {ok, t()} | parse_error() when
Purl :: uri_string:uri_string() | uri_string:uri_map() | t().
new(Purl) ->
maybe
{ok, Parsed} ?= purl_parser:parse(Purl),
purl_special_case:apply(Parsed)
end.
?DOC("""
Convert known URLs to purl
## Currently Supported
* GitHub: Repository HTTP / Git URL, Project URL
* BitBucket: Repository HTTTP / Git URL, Project URL
* Hex.pm package URL
""").
?DOC(#{since => <<"0.3.0">>}).
-spec from_resource_uri(Uri) -> {ok, t()} | error when
Uri :: uri_string:uri_map() | uri_string:uri_string().
from_resource_uri(Uri) -> purl_resource:from_uri(Uri).