aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIan Ling <ian@iancaling.com>2021-06-15 14:59:52 -0700
committerIan Ling <ian@iancaling.com>2021-06-16 11:28:39 -0700
commit9b060e5ac8c4614be5fd5f5e14561e8c4dff9ef2 (patch)
tree82ad0ddae5914e167ababa0188d4352320d8ab87
parent8574b91809e949442fa0cbf3174d9ae83cd69f60 (diff)
downloadspdx-tools-9b060e5ac8c4614be5fd5f5e14561e8c4dff9ef2.tar.gz
Add relationship filter function
Signed-off-by: Ian Ling <ian@iancaling.com>
-rw-r--r--spdxlib/described_elements.go104
-rw-r--r--spdxlib/described_elements_test.go56
-rw-r--r--spdxlib/documents.go66
-rw-r--r--spdxlib/documents_test.go185
-rw-r--r--spdxlib/element_ids.go15
-rw-r--r--spdxlib/element_ids_test.go16
-rw-r--r--spdxlib/relationships.go31
-rw-r--r--spdxlib/relationships_test.go145
8 files changed, 496 insertions, 122 deletions
diff --git a/spdxlib/described_elements.go b/spdxlib/described_elements.go
index 6158f88..e8373da 100644
--- a/spdxlib/described_elements.go
+++ b/spdxlib/described_elements.go
@@ -5,8 +5,6 @@ package spdxlib
import (
"fmt"
- "sort"
-
"github.com/spdx/tools-golang/spdx"
)
@@ -14,8 +12,7 @@ import (
// in this Document that it "describes," according to SPDX rules:
// - If the document has only one Package, its ID is returned.
// - If the document has 2+ Packages, it returns the IDs of those that have
-// a DESCRIBES (or DESCRIBED_BY) relationship to this DOCUMENT. If no
-// -
+// a DESCRIBES (or DESCRIBED_BY) relationship to this DOCUMENT.
func GetDescribedPackageIDs2_1(doc *spdx.Document2_1) ([]spdx.ElementID, error) {
// if nil Packages map or zero packages in it, return empty slice
if doc.Packages == nil {
@@ -37,40 +34,28 @@ func GetDescribedPackageIDs2_1(doc *spdx.Document2_1) ([]spdx.ElementID, error)
if doc.Relationships == nil {
return nil, fmt.Errorf("multiple Packages in Document but Relationships slice is nil")
}
- // collect IDs as strings so we can sort them easily
- eIDStrs := []string{}
- for _, rln := range doc.Relationships {
- if rln.Relationship == "DESCRIBES" && rln.RefA == spdx.MakeDocElementID("", "DOCUMENT") {
- // confirm RefB is actually a package in this document
- if _, ok := doc.Packages[rln.RefB.ElementRefID]; !ok {
- // if it's an unpackaged file, that's valid (no error) but don't return it
- if _, ok2 := doc.UnpackagedFiles[rln.RefB.ElementRefID]; !ok2 {
- return nil, fmt.Errorf("Document DESCRIBES %s but no such Package or unpackaged File", string(rln.RefB.ElementRefID))
- }
- }
- eIDStrs = append(eIDStrs, string(rln.RefB.ElementRefID))
- }
- if rln.Relationship == "DESCRIBED_BY" && rln.RefB == spdx.MakeDocElementID("", "DOCUMENT") {
- // confirm RefA is actually a package in this document
- // if it's an unpackaged file, that's valid (no error) but don't return it
- if _, ok := doc.Packages[rln.RefA.ElementRefID]; !ok {
- // if it's an unpackaged file, that's valid (no error) but don't return it
- if _, ok2 := doc.UnpackagedFiles[rln.RefA.ElementRefID]; !ok2 {
- return nil, fmt.Errorf("%s DESCRIBED_BY Document but no such Package or unpackaged File", string(rln.RefA.ElementRefID))
- }
- }
- eIDStrs = append(eIDStrs, string(rln.RefA.ElementRefID))
+
+ eIDs, err := FilterRelationships2_1(doc, func(relationship *spdx.Relationship2_1) *spdx.ElementID {
+ refDocument := spdx.MakeDocElementID("", "DOCUMENT")
+
+ if relationship.Relationship == "DESCRIBES" && relationship.RefA == refDocument {
+ return &relationship.RefB.ElementRefID
+ } else if relationship.Relationship == "DESCRIBED_BY" && relationship.RefB == refDocument {
+ return &relationship.RefA.ElementRefID
}
+
+ return nil
+ })
+ if err != nil {
+ return nil, err
}
- if len(eIDStrs) == 0 {
+
+ if len(eIDs) == 0 {
return nil, fmt.Errorf("no DESCRIBES or DESCRIBED_BY relationships found for this Document")
}
- // sort them, convert back to ElementIDs and return
- sort.Strings(eIDStrs)
- eIDs := []spdx.ElementID{}
- for _, eIDStr := range eIDStrs {
- eIDs = append(eIDs, spdx.ElementID(eIDStr))
- }
+
+ eIDs = SortElementIDs(eIDs)
+
return eIDs, nil
}
@@ -78,8 +63,7 @@ func GetDescribedPackageIDs2_1(doc *spdx.Document2_1) ([]spdx.ElementID, error)
// in this Document that it "describes," according to SPDX rules:
// - If the document has only one Package, its ID is returned.
// - If the document has 2+ Packages, it returns the IDs of those that have
-// a DESCRIBES (or DESCRIBED_BY) relationship to this DOCUMENT. If no
-// -
+// a DESCRIBES (or DESCRIBED_BY) relationship to this DOCUMENT.
func GetDescribedPackageIDs2_2(doc *spdx.Document2_2) ([]spdx.ElementID, error) {
// if nil Packages map or zero packages in it, return empty slice
if doc.Packages == nil {
@@ -101,39 +85,27 @@ func GetDescribedPackageIDs2_2(doc *spdx.Document2_2) ([]spdx.ElementID, error)
if doc.Relationships == nil {
return nil, fmt.Errorf("multiple Packages in Document but Relationships slice is nil")
}
- // collect IDs as strings so we can sort them easily
- eIDStrs := []string{}
- for _, rln := range doc.Relationships {
- if rln.Relationship == "DESCRIBES" && rln.RefA == spdx.MakeDocElementID("", "DOCUMENT") {
- // confirm RefB is actually a package in this document
- if _, ok := doc.Packages[rln.RefB.ElementRefID]; !ok {
- // if it's an unpackaged file, that's valid (no error) but don't return it
- if _, ok2 := doc.UnpackagedFiles[rln.RefB.ElementRefID]; !ok2 {
- return nil, fmt.Errorf("Document DESCRIBES %s but no such Package or unpackaged File", string(rln.RefB.ElementRefID))
- }
- }
- eIDStrs = append(eIDStrs, string(rln.RefB.ElementRefID))
- }
- if rln.Relationship == "DESCRIBED_BY" && rln.RefB == spdx.MakeDocElementID("", "DOCUMENT") {
- // confirm RefA is actually a package in this document
- // if it's an unpackaged file, that's valid (no error) but don't return it
- if _, ok := doc.Packages[rln.RefA.ElementRefID]; !ok {
- // if it's an unpackaged file, that's valid (no error) but don't return it
- if _, ok2 := doc.UnpackagedFiles[rln.RefA.ElementRefID]; !ok2 {
- return nil, fmt.Errorf("%s DESCRIBED_BY Document but no such Package or unpackaged File", string(rln.RefA.ElementRefID))
- }
- }
- eIDStrs = append(eIDStrs, string(rln.RefA.ElementRefID))
+
+ eIDs, err := FilterRelationships2_2(doc, func(relationship *spdx.Relationship2_2) *spdx.ElementID {
+ refDocument := spdx.MakeDocElementID("", "DOCUMENT")
+
+ if relationship.Relationship == "DESCRIBES" && relationship.RefA == refDocument {
+ return &relationship.RefB.ElementRefID
+ } else if relationship.Relationship == "DESCRIBED_BY" && relationship.RefB == refDocument {
+ return &relationship.RefA.ElementRefID
}
+
+ return nil
+ })
+ if err != nil {
+ return nil, err
}
- if len(eIDStrs) == 0 {
+
+ if len(eIDs) == 0 {
return nil, fmt.Errorf("no DESCRIBES or DESCRIBED_BY relationships found for this Document")
}
- // sort them, convert back to ElementIDs and return
- sort.Strings(eIDStrs)
- eIDs := []spdx.ElementID{}
- for _, eIDStr := range eIDStrs {
- eIDs = append(eIDs, spdx.ElementID(eIDStr))
- }
+
+ eIDs = SortElementIDs(eIDs)
+
return eIDs, nil
}
diff --git a/spdxlib/described_elements_test.go b/spdxlib/described_elements_test.go
index 4ba99da..32fa726 100644
--- a/spdxlib/described_elements_test.go
+++ b/spdxlib/described_elements_test.go
@@ -183,34 +183,6 @@ func Test2_1FailsToGetDescribedPackagesIfNilMap(t *testing.T) {
}
}
-func Test2_1FailsToGetDescribedPackagesIfRelationshipForNonexistantPackageID(t *testing.T) {
- // set up document and multiple packages, but no DESCRIBES relationships
- doc := &spdx.Document2_1{
- CreationInfo: &spdx.CreationInfo2_1{
- SPDXVersion: "SPDX-2.1",
- DataLicense: "CC0-1.0",
- SPDXIdentifier: spdx.ElementID("DOCUMENT"),
- },
- Packages: map[spdx.ElementID]*spdx.Package2_1{
- spdx.ElementID("p1"): &spdx.Package2_1{PackageName: "pkg1", PackageSPDXIdentifier: "p1"},
- spdx.ElementID("p2"): &spdx.Package2_1{PackageName: "pkg2", PackageSPDXIdentifier: "p2"},
- },
- Relationships: []*spdx.Relationship2_1{
- // different relationship
- &spdx.Relationship2_1{
- RefA: spdx.MakeDocElementID("", "DOCUMENT"),
- RefB: spdx.MakeDocElementID("", "p17"),
- Relationship: "DESCRIBES",
- },
- },
- }
-
- _, err := GetDescribedPackageIDs2_1(doc)
- if err == nil {
- t.Fatalf("expected non-nil error, got nil")
- }
-}
-
// ===== 2.2 tests =====
func Test2_2CanGetIDsOfDescribedPackages(t *testing.T) {
@@ -385,31 +357,3 @@ func Test2_2FailsToGetDescribedPackagesIfNilMap(t *testing.T) {
t.Fatalf("expected non-nil error, got nil")
}
}
-
-func Test2_2FailsToGetDescribedPackagesIfRelationshipForNonexistantPackageID(t *testing.T) {
- // set up document and multiple packages, but no DESCRIBES relationships
- doc := &spdx.Document2_2{
- CreationInfo: &spdx.CreationInfo2_2{
- SPDXVersion: "SPDX-2.2",
- DataLicense: "CC0-1.0",
- SPDXIdentifier: spdx.ElementID("DOCUMENT"),
- },
- Packages: map[spdx.ElementID]*spdx.Package2_2{
- spdx.ElementID("p1"): &spdx.Package2_2{PackageName: "pkg1", PackageSPDXIdentifier: "p1"},
- spdx.ElementID("p2"): &spdx.Package2_2{PackageName: "pkg2", PackageSPDXIdentifier: "p2"},
- },
- Relationships: []*spdx.Relationship2_2{
- // different relationship
- &spdx.Relationship2_2{
- RefA: spdx.MakeDocElementID("", "DOCUMENT"),
- RefB: spdx.MakeDocElementID("", "p17"),
- Relationship: "DESCRIBES",
- },
- },
- }
-
- _, err := GetDescribedPackageIDs2_2(doc)
- if err == nil {
- t.Fatalf("expected non-nil error, got nil")
- }
-}
diff --git a/spdxlib/documents.go b/spdxlib/documents.go
new file mode 100644
index 0000000..0f90a02
--- /dev/null
+++ b/spdxlib/documents.go
@@ -0,0 +1,66 @@
+package spdxlib
+
+import (
+ "fmt"
+ "github.com/spdx/tools-golang/spdx"
+)
+
+// ValidateDocument2_1 returns an error if the Document is found to be invalid, or nil if the Document is valid.
+// Currently, this only verifies that all Element IDs mentioned in Relationships exist in the Document as either a
+// Package or an UnpackagedFile.
+func ValidateDocument2_1(doc *spdx.Document2_1) error {
+ // cache a map of valid package IDs for quick lookups
+ validElementIDs := make(map[spdx.ElementID]bool)
+ for _, docPackage := range doc.Packages {
+ validElementIDs[docPackage.PackageSPDXIdentifier] = true
+ }
+
+ for _, unpackagedFile := range doc.UnpackagedFiles {
+ validElementIDs[unpackagedFile.FileSPDXIdentifier] = true
+ }
+
+ // add the Document element ID
+ validElementIDs[spdx.MakeDocElementID("", "DOCUMENT").ElementRefID] = true
+
+ for _, relationship := range doc.Relationships {
+ if !validElementIDs[relationship.RefA.ElementRefID] {
+ return fmt.Errorf("%s used in relationship but no such package exists", string(relationship.RefA.ElementRefID))
+ }
+
+ if !validElementIDs[relationship.RefB.ElementRefID] {
+ return fmt.Errorf("%s used in relationship but no such package exists", string(relationship.RefB.ElementRefID))
+ }
+ }
+
+ return nil
+}
+
+// ValidateDocument2_2 returns an error if the Document is found to be invalid, or nil if the Document is valid.
+// Currently, this only verifies that all Element IDs mentioned in Relationships exist in the Document as either a
+// Package or an UnpackagedFile.
+func ValidateDocument2_2(doc *spdx.Document2_2) error {
+ // cache a map of package IDs for quick lookups
+ validElementIDs := make(map[spdx.ElementID]bool)
+ for _, docPackage := range doc.Packages {
+ validElementIDs[docPackage.PackageSPDXIdentifier] = true
+ }
+
+ for _, unpackagedFile := range doc.UnpackagedFiles {
+ validElementIDs[unpackagedFile.FileSPDXIdentifier] = true
+ }
+
+ // add the Document element ID
+ validElementIDs[spdx.MakeDocElementID("", "DOCUMENT").ElementRefID] = true
+
+ for _, relationship := range doc.Relationships {
+ if !validElementIDs[relationship.RefA.ElementRefID] {
+ return fmt.Errorf("%s used in relationship but no such package exists", string(relationship.RefA.ElementRefID))
+ }
+
+ if !validElementIDs[relationship.RefB.ElementRefID] {
+ return fmt.Errorf("%s used in relationship but no such package exists", string(relationship.RefB.ElementRefID))
+ }
+ }
+
+ return nil
+}
diff --git a/spdxlib/documents_test.go b/spdxlib/documents_test.go
new file mode 100644
index 0000000..60a39b9
--- /dev/null
+++ b/spdxlib/documents_test.go
@@ -0,0 +1,185 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+
+package spdxlib
+
+import (
+ "testing"
+
+ "github.com/spdx/tools-golang/spdx"
+)
+
+// ===== 2.1 tests =====
+
+func Test2_1ValidDocumentPassesValidation(t *testing.T) {
+ // set up document and some packages and relationships
+ doc := &spdx.Document2_1{
+ CreationInfo: &spdx.CreationInfo2_1{
+ SPDXVersion: "SPDX-2.1",
+ DataLicense: "CC0-1.0",
+ SPDXIdentifier: spdx.ElementID("DOCUMENT"),
+ },
+ Packages: map[spdx.ElementID]*spdx.Package2_1{
+ spdx.ElementID("p1"): {PackageName: "pkg1", PackageSPDXIdentifier: "p1"},
+ spdx.ElementID("p2"): {PackageName: "pkg2", PackageSPDXIdentifier: "p2"},
+ spdx.ElementID("p3"): {PackageName: "pkg3", PackageSPDXIdentifier: "p3"},
+ spdx.ElementID("p4"): {PackageName: "pkg4", PackageSPDXIdentifier: "p4"},
+ spdx.ElementID("p5"): {PackageName: "pkg5", PackageSPDXIdentifier: "p5"},
+ },
+ Relationships: []*spdx.Relationship2_1{
+ {
+ RefA: spdx.MakeDocElementID("", "DOCUMENT"),
+ RefB: spdx.MakeDocElementID("", "p1"),
+ Relationship: "DESCRIBES",
+ },
+ {
+ RefA: spdx.MakeDocElementID("", "DOCUMENT"),
+ RefB: spdx.MakeDocElementID("", "p5"),
+ Relationship: "DESCRIBES",
+ },
+ // inverse relationship -- should also get detected
+ {
+ RefA: spdx.MakeDocElementID("", "p4"),
+ RefB: spdx.MakeDocElementID("", "DOCUMENT"),
+ Relationship: "DESCRIBED_BY",
+ },
+ // different relationship
+ {
+ RefA: spdx.MakeDocElementID("", "p1"),
+ RefB: spdx.MakeDocElementID("", "p2"),
+ Relationship: "DEPENDS_ON",
+ },
+ },
+ }
+
+ err := ValidateDocument2_1(doc)
+ if err != nil {
+ t.Fatalf("expected nil error, got: %s", err.Error())
+ }
+}
+
+func Test2_1InvalidDocumentFailsValidation(t *testing.T) {
+ // set up document and some packages and relationships
+ doc := &spdx.Document2_1{
+ CreationInfo: &spdx.CreationInfo2_1{
+ SPDXVersion: "SPDX-2.1",
+ DataLicense: "CC0-1.0",
+ SPDXIdentifier: spdx.ElementID("DOCUMENT"),
+ },
+ Packages: map[spdx.ElementID]*spdx.Package2_1{
+ spdx.ElementID("p1"): {PackageName: "pkg1", PackageSPDXIdentifier: "p1"},
+ spdx.ElementID("p2"): {PackageName: "pkg2", PackageSPDXIdentifier: "p2"},
+ spdx.ElementID("p3"): {PackageName: "pkg3", PackageSPDXIdentifier: "p3"},
+ },
+ Relationships: []*spdx.Relationship2_1{
+ {
+ RefA: spdx.MakeDocElementID("", "DOCUMENT"),
+ RefB: spdx.MakeDocElementID("", "p1"),
+ Relationship: "DESCRIBES",
+ },
+ {
+ RefA: spdx.MakeDocElementID("", "DOCUMENT"),
+ RefB: spdx.MakeDocElementID("", "p2"),
+ Relationship: "DESCRIBES",
+ },
+ // invalid ID p99
+ {
+ RefA: spdx.MakeDocElementID("", "p1"),
+ RefB: spdx.MakeDocElementID("", "p99"),
+ Relationship: "DEPENDS_ON",
+ },
+ },
+ }
+
+ err := ValidateDocument2_1(doc)
+ if err == nil {
+ t.Fatalf("expected non-nil error, got nil")
+ }
+}
+
+// ===== 2.2 tests =====
+
+func Test2_2ValidDocumentPassesValidation(t *testing.T) {
+ // set up document and some packages and relationships
+ doc := &spdx.Document2_2{
+ CreationInfo: &spdx.CreationInfo2_2{
+ SPDXVersion: "SPDX-2.2",
+ DataLicense: "CC0-1.0",
+ SPDXIdentifier: spdx.ElementID("DOCUMENT"),
+ },
+ Packages: map[spdx.ElementID]*spdx.Package2_2{
+ spdx.ElementID("p1"): {PackageName: "pkg1", PackageSPDXIdentifier: "p1"},
+ spdx.ElementID("p2"): {PackageName: "pkg2", PackageSPDXIdentifier: "p2"},
+ spdx.ElementID("p3"): {PackageName: "pkg3", PackageSPDXIdentifier: "p3"},
+ spdx.ElementID("p4"): {PackageName: "pkg4", PackageSPDXIdentifier: "p4"},
+ spdx.ElementID("p5"): {PackageName: "pkg5", PackageSPDXIdentifier: "p5"},
+ },
+ Relationships: []*spdx.Relationship2_2{
+ {
+ RefA: spdx.MakeDocElementID("", "DOCUMENT"),
+ RefB: spdx.MakeDocElementID("", "p1"),
+ Relationship: "DESCRIBES",
+ },
+ {
+ RefA: spdx.MakeDocElementID("", "DOCUMENT"),
+ RefB: spdx.MakeDocElementID("", "p5"),
+ Relationship: "DESCRIBES",
+ },
+ // inverse relationship -- should also get detected
+ {
+ RefA: spdx.MakeDocElementID("", "p4"),
+ RefB: spdx.MakeDocElementID("", "DOCUMENT"),
+ Relationship: "DESCRIBED_BY",
+ },
+ // different relationship
+ {
+ RefA: spdx.MakeDocElementID("", "p1"),
+ RefB: spdx.MakeDocElementID("", "p2"),
+ Relationship: "DEPENDS_ON",
+ },
+ },
+ }
+
+ err := ValidateDocument2_2(doc)
+ if err != nil {
+ t.Fatalf("expected nil error, got: %s", err.Error())
+ }
+}
+
+func Test2_2InvalidDocumentFailsValidation(t *testing.T) {
+ // set up document and some packages and relationships
+ doc := &spdx.Document2_2{
+ CreationInfo: &spdx.CreationInfo2_2{
+ SPDXVersion: "SPDX-2.2",
+ DataLicense: "CC0-1.0",
+ SPDXIdentifier: spdx.ElementID("DOCUMENT"),
+ },
+ Packages: map[spdx.ElementID]*spdx.Package2_2{
+ spdx.ElementID("p1"): {PackageName: "pkg1", PackageSPDXIdentifier: "p1"},
+ spdx.ElementID("p2"): {PackageName: "pkg2", PackageSPDXIdentifier: "p2"},
+ spdx.ElementID("p3"): {PackageName: "pkg3", PackageSPDXIdentifier: "p3"},
+ },
+ Relationships: []*spdx.Relationship2_2{
+ {
+ RefA: spdx.MakeDocElementID("", "DOCUMENT"),
+ RefB: spdx.MakeDocElementID("", "p1"),
+ Relationship: "DESCRIBES",
+ },
+ {
+ RefA: spdx.MakeDocElementID("", "DOCUMENT"),
+ RefB: spdx.MakeDocElementID("", "p5"),
+ Relationship: "DESCRIBES",
+ },
+ // invalid ID p99
+ {
+ RefA: spdx.MakeDocElementID("", "p1"),
+ RefB: spdx.MakeDocElementID("", "p99"),
+ Relationship: "DEPENDS_ON",
+ },
+ },
+ }
+
+ err := ValidateDocument2_2(doc)
+ if err == nil {
+ t.Fatalf("expected non-nil error, got nil")
+ }
+}
diff --git a/spdxlib/element_ids.go b/spdxlib/element_ids.go
new file mode 100644
index 0000000..2214d04
--- /dev/null
+++ b/spdxlib/element_ids.go
@@ -0,0 +1,15 @@
+package spdxlib
+
+import (
+ "github.com/spdx/tools-golang/spdx"
+ "sort"
+)
+
+// SortElementIDs sorts and returns the given slice of ElementIDs
+func SortElementIDs(eIDs []spdx.ElementID) []spdx.ElementID {
+ sort.Slice(eIDs, func(i, j int) bool {
+ return eIDs[i] < eIDs[j]
+ })
+
+ return eIDs
+} \ No newline at end of file
diff --git a/spdxlib/element_ids_test.go b/spdxlib/element_ids_test.go
new file mode 100644
index 0000000..c541e4f
--- /dev/null
+++ b/spdxlib/element_ids_test.go
@@ -0,0 +1,16 @@
+package spdxlib
+
+import (
+ "github.com/spdx/tools-golang/spdx"
+ "reflect"
+ "testing"
+)
+
+func TestSortElementIDs(t *testing.T) {
+ eIDs := []spdx.ElementID{"def", "abc", "123"}
+ eIDs = SortElementIDs(eIDs)
+
+ if !reflect.DeepEqual(eIDs, []spdx.ElementID{"123", "abc", "def"}) {
+ t.Fatalf("expected sorted ElementIDs, got: %v", eIDs)
+ }
+} \ No newline at end of file
diff --git a/spdxlib/relationships.go b/spdxlib/relationships.go
new file mode 100644
index 0000000..8b7422e
--- /dev/null
+++ b/spdxlib/relationships.go
@@ -0,0 +1,31 @@
+package spdxlib
+
+import "github.com/spdx/tools-golang/spdx"
+
+// FilterRelationships2_1 returns a slice of Element IDs returned by the given filter closure. The closure is passed
+// one relationship at a time, and it can return an ElementID or nil.
+func FilterRelationships2_1(doc *spdx.Document2_1, filter func(*spdx.Relationship2_1) *spdx.ElementID) ([]spdx.ElementID, error) {
+ elementIDs := []spdx.ElementID{}
+
+ for _, relationship := range doc.Relationships {
+ if id := filter(relationship); id != nil {
+ elementIDs = append(elementIDs, *id)
+ }
+ }
+
+ return elementIDs, nil
+}
+
+// FilterRelationships2_2 returns a slice of Element IDs returned by the given filter closure. The closure is passed
+// one relationship at a time, and it can return an ElementID or nil.
+func FilterRelationships2_2(doc *spdx.Document2_2, filter func(*spdx.Relationship2_2) *spdx.ElementID) ([]spdx.ElementID, error) {
+ elementIDs := []spdx.ElementID{}
+
+ for _, relationship := range doc.Relationships {
+ if id := filter(relationship); id != nil {
+ elementIDs = append(elementIDs, *id)
+ }
+ }
+
+ return elementIDs, nil
+}
diff --git a/spdxlib/relationships_test.go b/spdxlib/relationships_test.go
new file mode 100644
index 0000000..4a12a4a
--- /dev/null
+++ b/spdxlib/relationships_test.go
@@ -0,0 +1,145 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+
+package spdxlib
+
+import (
+ "testing"
+
+ "github.com/spdx/tools-golang/spdx"
+)
+
+// ===== 2.1 tests =====
+
+func Test2_1FilterForDependencies(t *testing.T) {
+ // set up document and some packages and relationships
+ doc := &spdx.Document2_1{
+ CreationInfo: &spdx.CreationInfo2_1{
+ SPDXVersion: "SPDX-2.1",
+ DataLicense: "CC0-1.0",
+ SPDXIdentifier: spdx.ElementID("DOCUMENT"),
+ },
+ Packages: map[spdx.ElementID]*spdx.Package2_1{
+ spdx.ElementID("p1"): {PackageName: "pkg1", PackageSPDXIdentifier: "p1"},
+ spdx.ElementID("p2"): {PackageName: "pkg2", PackageSPDXIdentifier: "p2"},
+ spdx.ElementID("p3"): {PackageName: "pkg3", PackageSPDXIdentifier: "p3"},
+ spdx.ElementID("p4"): {PackageName: "pkg4", PackageSPDXIdentifier: "p4"},
+ spdx.ElementID("p5"): {PackageName: "pkg5", PackageSPDXIdentifier: "p5"},
+ },
+ Relationships: []*spdx.Relationship2_1{
+ {
+ RefA: spdx.MakeDocElementID("", "DOCUMENT"),
+ RefB: spdx.MakeDocElementID("", "p1"),
+ Relationship: "DESCRIBES",
+ },
+ {
+ RefA: spdx.MakeDocElementID("", "DOCUMENT"),
+ RefB: spdx.MakeDocElementID("", "p5"),
+ Relationship: "DESCRIBES",
+ },
+ {
+ RefA: spdx.MakeDocElementID("", "p4"),
+ RefB: spdx.MakeDocElementID("", "DOCUMENT"),
+ Relationship: "DESCRIBED_BY",
+ },
+ {
+ RefA: spdx.MakeDocElementID("", "p1"),
+ RefB: spdx.MakeDocElementID("", "p2"),
+ Relationship: "DEPENDS_ON",
+ },
+ {
+ RefA: spdx.MakeDocElementID("", "p3"),
+ RefB: spdx.MakeDocElementID("", "p4"),
+ Relationship: "DEPENDENCY_OF",
+ },
+
+ },
+ }
+
+ eIDs, err := FilterRelationships2_1(doc, func(relationship *spdx.Relationship2_1) *spdx.ElementID {
+ p1EID := spdx.MakeDocElementID("", "p1")
+ if relationship.Relationship == "DEPENDS_ON" && relationship.RefA == p1EID {
+ return &relationship.RefB.ElementRefID
+ } else if relationship.Relationship == "DEPENDENCY_OF" && relationship.RefB == p1EID {
+ return &relationship.RefA.ElementRefID
+ }
+
+ return nil
+ })
+ if err != nil {
+ t.Fatalf("expected non-nil err, got: %s", err.Error())
+ }
+
+ if len(eIDs) != 1 {
+ t.Fatalf("expected 1 ElementID, got: %v", eIDs)
+ }
+
+ if eIDs[0] != spdx.MakeDocElementID("", "p2").ElementRefID {
+ t.Fatalf("received unexpected relationship: %v", eIDs[0])
+ }
+}
+
+// ===== 2.2 tests =====
+
+func Test2_2FindsDependsOnRelationships(t *testing.T) {
+ // set up document and some packages and relationships
+ doc := &spdx.Document2_2{
+ CreationInfo: &spdx.CreationInfo2_2{
+ SPDXVersion: "SPDX-2.2",
+ DataLicense: "CC0-1.0",
+ SPDXIdentifier: spdx.ElementID("DOCUMENT"),
+ },
+ Packages: map[spdx.ElementID]*spdx.Package2_2{
+ spdx.ElementID("p1"): {PackageName: "pkg1", PackageSPDXIdentifier: "p1"},
+ spdx.ElementID("p2"): {PackageName: "pkg2", PackageSPDXIdentifier: "p2"},
+ spdx.ElementID("p3"): {PackageName: "pkg3", PackageSPDXIdentifier: "p3"},
+ spdx.ElementID("p4"): {PackageName: "pkg4", PackageSPDXIdentifier: "p4"},
+ spdx.ElementID("p5"): {PackageName: "pkg5", PackageSPDXIdentifier: "p5"},
+ },
+ Relationships: []*spdx.Relationship2_2{
+ {
+ RefA: spdx.MakeDocElementID("", "DOCUMENT"),
+ RefB: spdx.MakeDocElementID("", "p1"),
+ Relationship: "DESCRIBES",
+ },
+ {
+ RefA: spdx.MakeDocElementID("", "DOCUMENT"),
+ RefB: spdx.MakeDocElementID("", "p5"),
+ Relationship: "DESCRIBES",
+ },
+ // inverse relationship -- should also get detected
+ {
+ RefA: spdx.MakeDocElementID("", "p4"),
+ RefB: spdx.MakeDocElementID("", "DOCUMENT"),
+ Relationship: "DESCRIBED_BY",
+ },
+ // different relationship
+ {
+ RefA: spdx.MakeDocElementID("", "p1"),
+ RefB: spdx.MakeDocElementID("", "p2"),
+ Relationship: "DEPENDS_ON",
+ },
+ },
+ }
+
+ eIDs, err := FilterRelationships2_2(doc, func(relationship *spdx.Relationship2_2) *spdx.ElementID {
+ p1EID := spdx.MakeDocElementID("", "p1")
+ if relationship.Relationship == "DEPENDS_ON" && relationship.RefA == p1EID {
+ return &relationship.RefB.ElementRefID
+ } else if relationship.Relationship == "DEPENDENCY_OF" && relationship.RefB == p1EID {
+ return &relationship.RefA.ElementRefID
+ }
+
+ return nil
+ })
+ if err != nil {
+ t.Fatalf("expected non-nil err, got: %s", err.Error())
+ }
+
+ if len(eIDs) != 1 {
+ t.Fatalf("expected 1 ElementID, got: %v", eIDs)
+ }
+
+ if eIDs[0] != spdx.MakeDocElementID("", "p2").ElementRefID {
+ t.Fatalf("received unexpected relationship: %v", eIDs[0])
+ }
+}