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
36 changes: 29 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
[![Version](https://img.shields.io/github/v/release/aboutcode-org/purlvalidator-go?style=for-the-badge)](https://github.com/aboutcode-org/purlvalidator-go/releases)
[![Test](https://img.shields.io/github/actions/workflow/status/aboutcode-org/purlvalidator-go/ci.yml?style=for-the-badge&logo=github)](https://github.com/aboutcode-org/purlvalidator-go/actions)

**purlvalidator** is a Go 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?**
**purlvalidator** is a Go 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?**

## How It Works?

**purlvalidator** is shipped with a pre-built FST (Finite State Transducer), a set of compact automata containing latest Package URLs mined by the MineCode[^1]. Library uses this FST to perform lookups and confirm whether the **base PURL**[^2] exists.
**purlvalidator** is shipped with a pre-built FST (Finite State Transducer), a set of compact automata containing latest Package-URLs mined by the MineCode[^1]. Library uses this FST to perform lookups and confirm whether the **base PURL**[^2] exists.

## Currently Supported Ecosystems

Expand All @@ -30,15 +30,37 @@
Add `purlvalidator` as dependency in your go.mod

```bash
require github.com/aboutcode-org/purlvalidator-go v0.1.0
require github.com/aboutcode-org/purlvalidator-go v1.0.0
```

Use it in your code like this
Use it in your code like this:

```rust
```go
import "github.com/aboutcode-org/purlvalidator-go"

var result bool = purlvalidator.Validate("pkg:nuget/FluentValidation");
func main() {
result, e := purlvalidator.Validate("pkg:nuget/FluentValidation");
if err != nil {
panic(err)
}
}
```

Examples and errors:
```go
// This will return: true
purlvalidator.Validate("pkg:nuget/FluentValidation");

// This will return: false
purlvalidator.Validate("pkg:nuget/non-existent-foo-bar");


// This will return an error: "only base PURL is supported (no version, qualifiers, or subpath)"
purlvalidator.Validate("pkg:nuget/FluentValidation@10.2.3");

// This will return an error: "purl scheme is not \"pkg\": \"pkddg\""
purlvalidator.Validate("test:nuget/FluentValidation");

```

## Contribution
Expand Down Expand Up @@ -90,4 +112,4 @@ limitations under the License.
```

[^1]: MineCode continuously collects package metadata from various package ecosystems to maintain an up-to-date catalog of known packages.
[^2]: A Base Package URL is a Package URL without a version or subpath.
[^2]: A Base Package-URL is a Package-URL without a version, qualifiers or subpath.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ go 1.22.3

require github.com/blevesearch/vellum v1.1.0

require github.com/package-url/packageurl-go v0.1.5

require (
github.com/bits-and-blooms/bitset v1.2.0 // indirect
github.com/blevesearch/mmap-go v1.0.4 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@ github.com/blevesearch/mmap-go v1.0.4 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCD
github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs=
github.com/blevesearch/vellum v1.1.0 h1:CinkGyIsgVlYf8Y2LUQHvdelgXr6PYuvoDIajq6yR9w=
github.com/blevesearch/vellum v1.1.0/go.mod h1:QgwWryE8ThtNPxtgWJof5ndPfx0/YMBh+W2weHKPw8Y=
github.com/package-url/packageurl-go v0.1.5 h1:O4efRXja2XQ5CtiiYiCZ22k/m7i5ugLiAghgcC+eDgk=
github.com/package-url/packageurl-go v0.1.5/go.mod h1:nKAWB8E6uk1MHqiS/lQb9pYBGH2+mdJ2PJc2s50dQY0=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
20 changes: 14 additions & 6 deletions purlvalidator.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ package purlvalidator

import (
_ "embed"
"fmt"
"log"
"strings"

"github.com/blevesearch/vellum"
"github.com/package-url/packageurl-go"
)

//go:embed purls.fst
Expand All @@ -32,12 +33,19 @@ func init() {
}
}

func validate_purl(packageURL string, fstMap *vellum.FST) bool {
packageURL = strings.TrimSuffix(packageURL, "/")
result, _ := fstMap.Contains([]byte(packageURL))
return result
func validate_purl(packageURL string, fstMap *vellum.FST) (bool, error) {
instance, err := packageurl.FromString(packageURL)
if err != nil {
return false, err
}
if instance.Version != "" || len(instance.Qualifiers) > 0 || instance.Subpath != "" {
return false, fmt.Errorf("only base PURL is supported (no version, qualifiers, or subpath)")
}

result, err := fstMap.Contains([]byte(packageURL))
return result, err
}

func Validate(packageURL string) bool {
func Validate(packageURL string) (bool, error) {
return validate_purl(packageURL, validator)
}
24 changes: 16 additions & 8 deletions purlvalidator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func setup() {

func TestNonexistentPurl(t *testing.T) {
purl := "pkg:nuget/nonexistent"
result := validate_purl(purl, testValidator)
result, _ := validate_purl(purl, testValidator)
expected := false

if result != expected {
Expand All @@ -57,20 +57,28 @@ func TestNonexistentPurl(t *testing.T) {

func TestValidPurl(t *testing.T) {
purl := "pkg:nuget/FluentUtils.FromCompositeAttribute"
result := validate_purl(purl, testValidator)
result, _ := validate_purl(purl, testValidator)
expected := true

if result != expected {
t.Errorf("validate_purl(\"%s\") = %t; expected %t", purl, result, expected)
}
}

func TestPurlWithTrailingSlash(t *testing.T) {
purl := "pkg:nuget/FluentUtils.FromCompositeAttribute/"
result := validate_purl(purl, testValidator)
expected := true
func TestErrorForInvalidPurl(t *testing.T) {
purl := "test:nuget/FluentUtils.FromCompositeAttribute"
_, err := validate_purl(purl, testValidator)

if result != expected {
t.Errorf("validate_purl(\"%s\") = %t; expected %t", purl, result, expected)
if err == nil {
t.Errorf("expected error but got nil")
}
}

func TestErrorForUnsupportedPurl(t *testing.T) {
purl := "pkg:nuget/EnterpriseLibrary.Common@6.0.1304"
_, err := validate_purl(purl, testValidator)

if err == nil {
t.Errorf("expected error but got nil")
}
}
Loading