diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..89009ba --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,53 @@ +name: Rust CI + +on: + workflow_dispatch: + pull_request: + push: + branches: + - main + +jobs: + build-test: + name: Run test and build + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + target: x86_64-unknown-linux-gnu + + - os: ubuntu-24.04-arm + target: aarch64-unknown-linux-gnu + + - os: macos-14 + target: aarch64-apple-darwin + + - os: macos-15 + target: aarch64-apple-darwin + + - os: macos-15-intel + target: x86_64-apple-darwin + + - os: windows-latest + target: x86_64-pc-windows-msvc + + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout source + uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + + - name: Check fmt and linting + run: make check + + - name: Run tests + run: cargo test --target ${{ matrix.target }} --verbose + + - name: Build + run: cargo build --target ${{ matrix.target }} --verbose diff --git a/Cargo.lock b/Cargo.lock index 5d1f28a..6d1c9c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,21 @@ version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ab85b9b05e3978cc9a9cf8fea7f01b494e1a09ed3037e16ba39edc7a29eb61a" +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "memmap2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" +dependencies = [ + "libc", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -19,5 +34,6 @@ name = "purl_validator" version = "0.1.0" dependencies = [ "fst", + "memmap2", "once_cell", ] diff --git a/Cargo.toml b/Cargo.toml index 427e57b..7962662 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ crate-type = ["rlib"] [dependencies] fst = "0.4.7" +memmap2 = "0.9.9" once_cell = "1.21" [[bin]] diff --git a/Makefile b/Makefile index 331be53..61f722f 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,20 @@ build-fst: cargo run --bin fst_builder - clean: cargo clean rm -f purls.fst rm -rf target -.PHONY: build-fst clean \ No newline at end of file +test: + cargo test + +valid: + cargo fmt --all + cargo clippy --all-targets --all-features + +check: + cargo fmt --all -- --check + cargo clippy --all-targets --all-features -- -D warnings + +.PHONY: build-fst clean test valid check \ No newline at end of file diff --git a/README.md b/README.md index a4fe7ba..7ae0298 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![License](https://img.shields.io/badge/License-Apache--2.0-blue.svg?style=for-the-badge)](https://opensource.org/licenses/Apache-2.0) [![Version](https://img.shields.io/github/v/release/aboutcode-org/purl-validator-rust?style=for-the-badge)](https://github.com/aboutcode-org/purl-validator-rust/releases) -[![Test](https://img.shields.io/github/actions/workflow/status/aboutcode-org/purl-validator-rust/run-test.yml?style=for-the-badge&logo=github)](https://github.com/aboutcode-org/purl-validator-rust/actions) +[![Test](https://img.shields.io/github/actions/workflow/status/aboutcode-org/purl-validator-rust/ci.yml?style=for-the-badge&logo=github)](https://github.com/aboutcode-org/purl-validator-rust/actions) **purl-validator** is a Rust library for validating [Package URLs (PURLs)](https://github.com/package-url/purl-spec). It works fully offline, including in **air-gapped** or **restricted environments**, and answers one key question: **Does the package this PURL represents actually exist?** @@ -53,6 +53,12 @@ Run tests: make test ``` +Fix formatting and linting: + +```bash +make valid +``` + ## License SPDX-License-Identifier: Apache-2.0 diff --git a/src/lib.rs b/src/lib.rs index a2f6085..83fc069 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,25 @@ use fst::Set; +use memmap2::Mmap; use once_cell::sync::Lazy; +use std::env; +use std::fs::File; +use std::path::Path; -static FST_BYTES: &[u8] = include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/purls.fst")); +static VALIDATOR: Lazy> = Lazy::new(|| { + let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("purls.fst"); + load_fst(&path) +}); -static VALIDATOR: Lazy> = - Lazy::new(|| Set::new(FST_BYTES).expect("Failed to load FST from embedded bytes")); +fn load_fst(path: &Path) -> Set { + let file = File::open(path).expect("Failed to open FST file"); + let mmap = unsafe { Mmap::map(&file).expect("Failed to mmap FST file") }; + Set::new(mmap).expect("Failed to load FST from mmap") +} pub fn validate(packageurl: &str) -> bool { let trimmed_packageurl = packageurl.trim_end_matches("/"); VALIDATOR.contains(trimmed_packageurl) } + +#[cfg(test)] +mod validate_tests; diff --git a/src/validate_tests.rs b/src/validate_tests.rs new file mode 100644 index 0000000..dff38d4 --- /dev/null +++ b/src/validate_tests.rs @@ -0,0 +1,11 @@ +use super::*; +use std::path::Path; + +#[test] +fn test_validate_with_custom_file() { + let test_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/data/test_purls.fst"); + let validator = load_fst(&test_path); + + assert!(validator.contains("pkg:nuget/FluentUtils.EnumExtensions")); + assert!(!validator.contains("pkg:example/nonexistent")); +} diff --git a/tests/data/test_purls.fst b/tests/data/test_purls.fst new file mode 100644 index 0000000..6145c8c Binary files /dev/null and b/tests/data/test_purls.fst differ