Skip to content

Commit 04280ae

Browse files
authored
Merge pull request #19 from aboutcode-org/throw-error
Return error for invalid or unsupported PURLs
2 parents 60e1e43 + 9b12406 commit 04280ae

5 files changed

Lines changed: 114 additions & 30 deletions

File tree

Cargo.lock

Lines changed: 31 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ include = [
2424

2525
[dependencies]
2626
fst = "0.4.7"
27-
memmap2 = "0.9.9"
27+
packageurl = "0.6.0"
2828
once_cell = "1.21"
2929

3030
[build-dependencies]

README.md

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,34 @@ Add `purl-validator` to your Rust dependency
3333
cargo add purl-validator
3434
```
3535

36-
Use it in your code like this
36+
Use it in your code like this:
3737

3838
```rust
3939
use purl_validator::validate;
4040

41-
let result: bool = validate("pkg:nuget/FluentValidation");
41+
fn main() {
42+
let result: bool = validate("pkg:nuget/FluentValidation")
43+
.expect("only fails if PURL is invalid or contains version, qualifier, or subpath");
44+
}
45+
```
46+
47+
Examples and errors:
48+
49+
```rust
50+
fn example() {
51+
// This will return: Ok(true)
52+
validate("pkg:nuget/FluentValidation");
53+
54+
// This will return: Ok(false)
55+
validate("pkg:nuget/non-existent-foo-bar");
56+
57+
58+
// This will return an error: Err(UnsupportedPurl("only base PURL is supported (no version, qualifiers, or subpath)"))
59+
validate("pkg:nuget/FluentValidation@10.2.3");
60+
61+
// This will return an error: Err(InvalidPurl(""))
62+
validate("nuget/FluentValidation");
63+
}
4264
```
4365

4466
## Contribution
@@ -90,4 +112,4 @@ limitations under the License.
90112
```
91113

92114
[^1]: MineCode continuously collects package metadata from various package ecosystems to maintain an up-to-date catalog of known packages.
93-
[^2]: A Base Package URL is a Package URL without a version or subpath.
115+
[^2]: A Base Package URL is a Package URL without a version, qualifiers or subpath.

src/lib.rs

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,35 +24,55 @@ See https://aboutcode.org for more information about nexB OSS projects.
2424
//! ```
2525
//! use purl_validator::validate;
2626
//!
27-
//! let result: bool = validate("pkg:nuget/FluentValidation");
27+
//! let result: bool = validate("pkg:nuget/FluentValidation")
28+
//! .expect("only fails if PURL is invalid or contains version, qualifier, or subpath");
2829
//! ```
2930
//!
3031
3132
use fst::Set;
32-
3333
use once_cell::sync::Lazy;
34+
use packageurl::PackageUrl;
3435
use std::env;
36+
use std::str::FromStr;
3537

3638
static FST_DATA: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/purls.fst"));
3739

3840
static VALIDATOR: Lazy<Set<&'static [u8]>> =
3941
Lazy::new(|| Set::new(FST_DATA).expect("Failed to load FST from embedded bytes"));
4042

41-
fn strip_and_check_purl(packageurl: &str, fst_map: &Set<&[u8]>) -> bool {
43+
fn strip_and_check_purl(packageurl: &str, fst_map: &Set<&[u8]>) -> Result<bool, ValidateError> {
44+
let purl = PackageUrl::from_str(packageurl).map_err(ValidateError::InvalidPurl)?;
45+
if purl.version().is_some() || !purl.qualifiers().is_empty() || purl.subpath().is_some() {
46+
return Err(ValidateError::UnsupportedPurl(
47+
"only base PURL is supported (no version, qualifiers, or subpath)",
48+
));
49+
}
50+
4251
let trimmed_packageurl = packageurl.trim_end_matches("/");
43-
fst_map.contains(trimmed_packageurl)
52+
Ok(fst_map.contains(trimmed_packageurl))
4453
}
4554

4655
/// Validate a Package URL (PURL)
4756
///
48-
/// Returns `true` if the given base PURL represents an existing package,
49-
/// otherwise returns `false`.
57+
/// Return `Ok(true)` if given **base PURL** represents an existing package,
58+
/// `Ok(false)` if it does not, or `Err` if the PURL is invalid or contains
59+
/// unsupported fields (version, qualifiers, or subpath).
60+
///
61+
/// A **base PURL** is a PURL without a version, qualifiers, or subpath.
62+
/// PURLs containing a version, qualifiers, or subpath are **not supported**
63+
/// and will cause the validator to return an error.
5064
///
5165
/// Use pre-built FST (Finite State Transducer) to perform lookups and confirm whether
5266
/// the **base PURL** exists.
53-
pub fn validate(packageurl: &str) -> bool {
67+
pub fn validate(packageurl: &str) -> Result<bool, ValidateError> {
5468
strip_and_check_purl(packageurl, &VALIDATOR)
5569
}
5670

71+
#[derive(Debug)]
72+
pub enum ValidateError {
73+
InvalidPurl(packageurl::Error),
74+
UnsupportedPurl(&'static str),
75+
}
76+
5777
#[cfg(test)]
5878
mod validate_tests;

src/validate_tests.rs

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@ fn test_validate_with_custom_file() {
2020
let data: Vec<u8> = fs::read(test_path).unwrap();
2121
let data_slice: &[u8] = &data;
2222
let validator = Set::new(data_slice).unwrap();
23-
assert!(strip_and_check_purl(
24-
"pkg:nuget/FluentUtils.EnumExtensions",
25-
&validator
26-
));
27-
assert!(!strip_and_check_purl("pkg:example/nonexistent", &validator));
23+
24+
let result = strip_and_check_purl("pkg:nuget/FluentUtils.EnumExtensions", &validator).unwrap();
25+
assert!(result);
26+
27+
let result = strip_and_check_purl("pkg:example/nonexistent", &validator).unwrap();
28+
assert!(!result);
2829
}
2930

3031
#[test]
@@ -35,8 +36,28 @@ fn test_validate_with_packageurl_trailing_slash() {
3536
let validator = Set::new(data_slice).unwrap();
3637

3738
assert!(validator.contains("pkg:nuget/FluentUtils.EnumExtensions"));
38-
assert!(strip_and_check_purl(
39-
"pkg:nuget/FluentUtils.EnumExtensions/",
40-
&validator
41-
));
39+
let result = strip_and_check_purl("pkg:nuget/FluentUtils.EnumExtensions/", &validator).unwrap();
40+
assert!(result);
41+
}
42+
43+
#[test]
44+
fn test_validate_with_packageurl_invalid_purl() {
45+
let test_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/data/test_purls.fst");
46+
let data: Vec<u8> = fs::read(test_path).unwrap();
47+
let data_slice: &[u8] = &data;
48+
let validator = Set::new(data_slice).unwrap();
49+
50+
let result = strip_and_check_purl("nuget/foobar", &validator);
51+
assert!(matches!(result, Err(ValidateError::InvalidPurl(_))));
52+
}
53+
54+
#[test]
55+
fn test_validate_with_packageurl_unsupported_purl() {
56+
let test_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/data/test_purls.fst");
57+
let data: Vec<u8> = fs::read(test_path).unwrap();
58+
let data_slice: &[u8] = &data;
59+
let validator = Set::new(data_slice).unwrap();
60+
61+
let result = strip_and_check_purl("pkg:nuget/FluentUtils.EnumExtensions@1.0.0", &validator);
62+
assert!(matches!(result, Err(ValidateError::UnsupportedPurl(_))));
4263
}

0 commit comments

Comments
 (0)