Skip to content
Merged
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
23 changes: 11 additions & 12 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,35 +1,34 @@
[package]
name = "packageurl"
version = "0.5.0"
edition = "2021"
version = "0.6.0"
edition = "2024"
authors = [
"Martin Larralde <martin.larralde@embl.de>",
"Jens Reimann <ctron@dentrassi.de>",
]
license = "MIT"
description = "Rust implementation of the package url specification"
documentation = "https://docs.rs/packageurl"
repository = "https://github.com/scm-rs/packageurl-rs"
repository = "https://github.com/scm-rs/packageurl.rs"
readme = "README.md"
keywords = ["purl", "package-url"]
categories = ["parser-implementations", "encoding", "development-tools"]
rust-version = "1.82.0"
rust-version = "1.85.0"

[dependencies]
percent-encoding = "2.1.0"
thiserror = "2.0.12"
percent-encoding = "2"
thiserror = "2"

memchr = { version = "2.4.0", optional = true }
serde = { version = "1.0.0", optional = true, features = ["derive"] }
memchr = { version = "2", optional = true }
serde = { version = "1", optional = true, features = ["derive"] }

[features]
default = []

[dev-dependencies]
criterion = "0.5.1"
rstest = "0.25.0"
serde = { version = "1.0.0", features = ["derive"] }
serde_json = "1.0.13"
criterion = "0.7"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
url = "2"

[[bench]]
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#

`packageurl-rs` [![Star me](https://img.shields.io/github/stars/scm-rs/packageurl.rs.svg?style=social&label=Star)](https://github.com/scm-rs/packageurl.rs/stargazers)
`packageurl.rs` [![Star me](https://img.shields.io/github/stars/scm-rs/packageurl.rs.svg?style=social&label=Star)](https://github.com/scm-rs/packageurl.rs/stargazers)

*Read and generate Package URLs in Rust.*

Expand Down Expand Up @@ -74,7 +74,7 @@ a changelog as part of the [GitHub releases](https://github.com/scm-rs/packageur
## 💭 Feedback

Found a bug? Have an enhancement request? Head over to the
[GitHub issue tracker](https://github.com/scm-rs/packageurl-rs/issues) of the project if
[GitHub issue tracker](https://github.com/scm-rs/packageurl.rs/issues) of the project if
you need to report or ask something. If you are filling in on a bug, please include as much
information as you can about the issue, and try to recreate the same bug in a simple, easily
reproducible situation.
Expand Down
2 changes: 1 addition & 1 deletion benches/bench.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use criterion::{criterion_group, criterion_main, Criterion};
use criterion::{Criterion, criterion_group, criterion_main};
use packageurl::PackageUrl;
use std::str::FromStr;

Expand Down
2 changes: 2 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ pub enum Error {
InvalidKey(String),
#[error("missing name")]
MissingName,
#[error("no namespace allowed for type {0:?}")]
TypeProhibitsNamespace(String),
#[error("invalid namespace component: {0:?}")]
InvalidNamespaceComponent(String),
#[error("missing scheme")]
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
//! [`FromStr`]: https://doc.rust-lang.org/std/str/trait.FromStr.html
//! [`PackageUrl`]: example_generated/struct.PackageUrl.html
//! [`'static`]: https://doc.rust-lang.org/reference/items/static-items.html#static-lifetime-elision
#![doc(issue_tracker_base_url = "https://github.com/althonos/packageurl-rs/issues/")]
#![doc(issue_tracker_base_url = "https://github.com/althonos/packageurl.rs/issues/")]

mod errors;
mod parser;
Expand Down
61 changes: 45 additions & 16 deletions src/purl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize};
use super::errors::Error;
use super::errors::Result;
use super::parser;
use super::utils::{to_lowercase, PercentCodec};
use super::utils::{PercentCodec, to_lowercase};
use super::validation;

const ENCODE_SET: &AsciiSet = &percent_encoding::CONTROLS
Expand All @@ -26,8 +26,6 @@ const ENCODE_SET: &AsciiSet = &percent_encoding::CONTROLS
.add(b'?')
.add(b'{')
.add(b'}')
// .add(b'/')
// .add(b':')
.add(b';')
.add(b'=')
.add(b'+')
Expand All @@ -36,7 +34,8 @@ const ENCODE_SET: &AsciiSet = &percent_encoding::CONTROLS
.add(b'[')
.add(b']')
.add(b'^')
.add(b'|');
.add(b'|')
.add(b'/');

/// A Package URL.
#[derive(Debug, Clone, PartialEq, Eq)]
Expand Down Expand Up @@ -66,7 +65,7 @@ impl<'a> PackageUrl<'a> {
/// cannot contain spaces.
///
/// # Name
/// The package name will be canonicalize depending on the type: for instance,
/// The package name will be canonicalized depending on the type: for instance,
/// 'bitbucket' packages have a case-insensitive name, so the name will be
/// lowercased if needed.
///
Expand All @@ -87,7 +86,7 @@ impl<'a> PackageUrl<'a> {
t = to_lowercase(t);
// lowercase name if required by type and needed
match t.as_ref() {
"bitbucket" | "deb" | "github" | "hex" | "npm" => {
"bitbucket" | "deb" | "github" | "hex" | "npm" | "composer" | "mlflow" => {
n = to_lowercase(n);
}
"pypi" => {
Expand Down Expand Up @@ -152,20 +151,31 @@ impl<'a> PackageUrl<'a> {
}

/// Assign a namespace to the package.
pub fn with_namespace<N>(&mut self, namespace: N) -> &mut Self
pub fn with_namespace<N>(&mut self, namespace: N) -> Result<&mut Self>
where
N: Into<Cow<'a, str>>,
{
// Fail if namespace is prohibited for this type
match self.ty.as_ref() {
"bitnami" | "cargo" | "cocoapods" | "conda" | "cran" | "gem" | "hackage" | "mlflow"
| "nuget" | "oci" | "pub" | "pypi" => {
return Err(Error::TypeProhibitsNamespace(self.ty.to_string()));
}
_ => {}
}

// Lowercase namespace if needed for this type
let mut n = namespace.into();
match self.ty.as_ref() {
"bitbucket" | "deb" | "github" | "golang" | "hex" | "rpm" => {
"apk" | "bitbucket" | "composer" | "deb" | "github" | "golang" | "hex" | "qpkg"
| "rpm" => {
n = to_lowercase(n);
}
_ => {}
}

self.namespace = Some(n);
self
Ok(self)
}

/// Clear the namespace
Expand All @@ -175,12 +185,17 @@ impl<'a> PackageUrl<'a> {
}

/// Assign a version to the package.
pub fn with_version<V>(&mut self, version: V) -> &mut Self
pub fn with_version<V>(&mut self, version: V) -> Result<&mut Self>
where
V: Into<Cow<'a, str>>,
{
self.version = Some(version.into());
self
let mut v = version.into();
if self.ty.as_ref() == "huggingface" {
v = to_lowercase(v);
}

self.version = Some(v);
Ok(self)
}

/// Clear the version
Expand Down Expand Up @@ -263,10 +278,10 @@ impl FromStr for PackageUrl<'static> {

let mut purl = Self::new(ty, name)?;
if let Some(ns) = namespace {
purl.with_namespace(ns);
purl.with_namespace(ns)?;
}
if let Some(v) = version {
purl.with_version(v);
purl.with_version(v)?;
}
if let Some(sp) = subpath {
purl.with_subpath(sp)?;
Expand Down Expand Up @@ -367,7 +382,9 @@ mod tests {
let purl_string = PackageUrl::new("type", "name")
.unwrap()
.with_namespace("name/space")
.unwrap()
.with_version("version")
.unwrap()
.with_subpath("sub/path")
.unwrap()
.add_qualifier("k1", "v1")
Expand All @@ -380,11 +397,20 @@ mod tests {

#[test]
fn test_percent_encoding_idempotent() {
let orig = "pkg:brew/openssl%25401.1@1.1.1w";
let orig = "pkg:brew/open%2Fssl%25401.1@1.1.1w";
let round_trip = orig.parse::<PackageUrl>().unwrap().to_string();
assert_eq!(orig, round_trip);
}

#[test]
fn test_percent_encoded_name() {
let raw_purl = "pkg:type/name/space/first%2Fname";
let purl = PackageUrl::from_str(raw_purl).unwrap();
assert_eq!(purl.ty(), "type");
assert_eq!(purl.namespace(), Some("name/space"));
assert_eq!(purl.name(), "first/name");
}

#[test]
fn test_percent_encoding_qualifier() {
let mut purl = "pkg:deb/ubuntu/gnome-calculator@1:41.1-2ubuntu2"
Expand All @@ -396,7 +422,10 @@ mod tests {
)
.unwrap();
let encoded = purl.to_string();
assert_eq!(encoded, "pkg:deb/ubuntu/gnome-calculator@1:41.1-2ubuntu2?vcs_url=git%2Bhttps://salsa.debian.org/gnome-team/gnome-calculator.git%40debian/1%2541.1-2");
assert_eq!(
encoded,
"pkg:deb/ubuntu/gnome-calculator@1:41.1-2ubuntu2?vcs_url=git%2Bhttps:%2F%2Fsalsa.debian.org%2Fgnome-team%2Fgnome-calculator.git%40debian%2F1%2541.1-2"
);
}

#[cfg(feature = "serde")]
Expand Down
5 changes: 2 additions & 3 deletions tests/spec/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,11 @@ pub fn run_build_test(case: &SpecTestCase) {

let mut purl = purl_result.unwrap();
if let Some(ref ns) = input.namespace {
purl.with_namespace(ns.as_ref());
let _ = purl.with_namespace(ns.as_ref());
}

if let Some(ref v) = input.version {
purl.with_version(v.as_ref());
let _ = purl.with_version(v.as_ref());
}

if let Some(ref sp) = input.subpath {
Expand Down Expand Up @@ -125,7 +125,6 @@ pub fn run_tests_from_spec(path: &Path) {
}
}


macro_rules! generate_json_tests {
($($test_name:ident => $file_path:expr),* $(,)?) => {
$(
Expand Down
9 changes: 4 additions & 5 deletions tests/spec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,15 @@ mod testcase;
generate_json_tests! {
alpm_test => "tests/spec/purl-spec/tests/types/alpm-test.json",
apk_test => "tests/spec/purl-spec/tests/types/apk-test.json",
bintray_test => "tests/spec/purl-spec/tests/types/bintray-test.json",
bitbucket_test => "tests/spec/purl-spec/tests/types/bitbucket-test.json",
bitnami_test => "tests/spec/purl-spec/tests/types/bitnami-test.json",
cargo_test => "tests/spec/purl-spec/tests/types/cargo-test.json",
cocoapods_test => "tests/spec/purl-spec/tests/types/cocoapods-test.json",
// composer_test => "tests/spec/purl-spec/tests/types/composer-test.json",
composer_test => "tests/spec/purl-spec/tests/types/composer-test.json",
// conan_test => "tests/spec/purl-spec/tests/types/conan-test.json",
conda_test => "tests/spec/purl-spec/tests/types/conda-test.json",
// cpan_test => "tests/spec/purl-spec/tests/types/cpan-test.json",
// cran_test => "tests/spec/purl-spec/tests/types/cran-test.json",
cran_test => "tests/spec/purl-spec/tests/types/cran-test.json",
deb_test => "tests/spec/purl-spec/tests/types/deb-test.json",
docker_test => "tests/spec/purl-spec/tests/types/docker-test.json",
gem_test => "tests/spec/purl-spec/tests/types/gem-test.json",
Expand All @@ -26,9 +25,9 @@ generate_json_tests! {
golang_test => "tests/spec/purl-spec/tests/types/golang-test.json",
hackage_test => "tests/spec/purl-spec/tests/types/hackage-test.json",
hex_test => "tests/spec/purl-spec/tests/types/hex-test.json",
// huggingface_test => "tests/spec/purl-spec/tests/types/huggingface-test.json",
huggingface_test => "tests/spec/purl-spec/tests/types/huggingface-test.json",
luarocks_test => "tests/spec/purl-spec/tests/types/luarocks-test.json",
// maven_test => "tests/spec/purl-spec/tests/types/maven-test.json",
maven_test => "tests/spec/purl-spec/tests/types/maven-test.json",
// mlflow_test => "tests/spec/purl-spec/tests/types/mlflow-test.json",
// npm_test => "tests/spec/purl-spec/tests/types/npm-test.json",
nuget_test => "tests/spec/purl-spec/tests/types/nuget-test.json",
Expand Down
2 changes: 1 addition & 1 deletion tests/spec/purl-spec
Submodule purl-spec updated 138 files