Type Alias purl::Purl

source ·
pub type Purl = GenericPurl<PackageType>;
Available on crate feature package-type only.
Expand description

A PURL that supports the known package types.

The spec lists many types, and some of the types may not be correct or fully described. Rather than implementing an exhaustive list and potentially claiming to support something that is incorrectly implemented, only some types are supported here. (see also package-url/purl-spec#38)

If you need additional types or different behavior, you can provide your own PurlShape implementation.

§Differences compared to current PURL spec

  • The PURL spec says that NuGet package names are case sensitive, but this implementation converts them to lowercase. This is consistent with the behavior of NuGet, which requires clients to convert package names to lowercase before calling the v3 package API. (package-url/purl-spec#226)
  • The PURL spec’s implementation of Python package name normalization is incomplete. In addition to converting dashes to underscores, periods are also converted to underscores, and consequitive underscores are combined into single underscores. This implementation matches the Python behavior. (package-url/purl-spec#165)
  • The PURL spec says that NPM package names are case insensitive, but this implementation does not convert them to lowercase. New NPM packages must have lowercase names, but there are already NPM packages in existance with uppercase names and those packages are distinct from other packages that have the same name in lowercase. (package-url/purl-spec#136)
  • The PURL spec says that Go package namespaces and names must be lowercase, but this implementation does not convert them to lowercase. Go modules can have mixed case names, and mixed case names are distinct. (package-url/purl-spec#196)
  • Some implementations treat ‘+’ in qualifiers as ‘+’ and some implementations treat ‘+’ as ’ ’. This implementation treats ‘+’ as ‘+’ because there is nothing in the spec that says they should be ’ ’. However, even though the spec never references x-www-form-urlencoded, qualifiers look like x-www-form-urlencoded, and in x-www-form-urlencoded, ‘+’ means ’ ’. For compatibility with other implementations, this implementation escapes ‘+’ as %2B in qualifiers, avoiding ambiguous parsing at the cost of making the PURL more difficult for humans to read. Some implementations also convert ‘+’ to ’ ’ in other parts of the PURL, including in version numbers where they can be common, but this implementation does not escape ‘+’ there because that is an implementation error, not a spec ambiguity.

§Extending PackageType

If you want to extend PackageType with support for another package type, you can do so via delegation.

use std::borrow::Cow;
use std::str::FromStr;

use purl::{
    GenericPurl, GenericPurlBuilder, PackageError, PackageType, PurlParts, PurlShape,
    UnsupportedPackageType,
};

#[derive(Clone, Copy)]
enum MyPackageType {
    PackageType(PackageType),
    Custom,
}

type Purl = GenericPurl<MyPackageType>;

impl FromStr for MyPackageType {
    type Err = UnsupportedPackageType;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        // Always try your types first.
        // Otherwise there may be unexpected behavioral changes if PackageType starts
        // supporting your types.
        if s.eq_ignore_ascii_case("custom") {
            Ok(MyPackageType::Custom)
        } else {
            PackageType::from_str(s).map(MyPackageType::PackageType)
        }
    }
}

impl PurlShape for MyPackageType {
    type Error = PackageError;

    fn package_type(&self) -> Cow<str> {
        match self {
            MyPackageType::PackageType(t) => t.package_type(),
            MyPackageType::Custom => Cow::Borrowed("custom"),
        }
    }

    fn finish(&mut self, parts: &mut PurlParts) -> Result<(), Self::Error> {
        match self {
            MyPackageType::PackageType(t) => t.finish(parts),
            MyPackageType::Custom => {
                // your logic here
                Ok(())
            },
        }
    }
}

assert!(matches!(
    Purl::from_str("pkg:custom/example").unwrap().package_type(),
    MyPackageType::Custom,
));

Aliased Type§

struct Purl { /* private fields */ }