From d326e1063b8265703fae4677e7fd63eb97e2df28 Mon Sep 17 00:00:00 2001 From: Keith Zantow Date: Tue, 11 Oct 2022 13:28:47 -0400 Subject: chore: update a few missed functions for 2.3 Signed-off-by: Keith Zantow --- spdxlib/described_elements.go | 52 ++++++++++++ spdxlib/described_elements_test.go | 170 +++++++++++++++++++++++++++++++++++++ spdxlib/documents.go | 31 +++++++ spdxlib/documents_test.go | 87 +++++++++++++++++++ spdxlib/relationships.go | 15 ++++ 5 files changed, 355 insertions(+) diff --git a/spdxlib/described_elements.go b/spdxlib/described_elements.go index 61833b4..a2a6356 100644 --- a/spdxlib/described_elements.go +++ b/spdxlib/described_elements.go @@ -9,6 +9,7 @@ import ( "github.com/spdx/tools-golang/spdx/common" "github.com/spdx/tools-golang/spdx/v2_1" "github.com/spdx/tools-golang/spdx/v2_2" + "github.com/spdx/tools-golang/spdx/v2_3" ) // GetDescribedPackageIDs2_1 returns a slice of ElementIDs for all Packages @@ -112,3 +113,54 @@ func GetDescribedPackageIDs2_2(doc *v2_2.Document) ([]common.ElementID, error) { return eIDs, nil } + +// GetDescribedPackageIDs2_3 returns a slice of ElementIDs for all Packages +// 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. +func GetDescribedPackageIDs2_3(doc *v2_3.Document) ([]common.ElementID, error) { + // if nil Packages map or zero packages in it, return empty slice + if doc.Packages == nil { + return nil, fmt.Errorf("Packages map is nil") + } + if len(doc.Packages) == 0 { + return nil, fmt.Errorf("no Packages in Document") + } + if len(doc.Packages) == 1 { + // get first (only) one and return its ID + for _, pkg := range doc.Packages { + return []common.ElementID{pkg.PackageSPDXIdentifier}, nil + } + } + + // two or more packages, so we need to go through the relationships, + // find DESCRIBES or DESCRIBED_BY for this DOCUMENT, verify they are + // valid IDs in this document's packages, and return them + if doc.Relationships == nil { + return nil, fmt.Errorf("multiple Packages in Document but Relationships slice is nil") + } + + eIDs, err := FilterRelationships2_3(doc, func(relationship *v2_3.Relationship) *common.ElementID { + refDocument := common.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(eIDs) == 0 { + return nil, fmt.Errorf("no DESCRIBES or DESCRIBED_BY relationships found for this Document") + } + + eIDs = SortElementIDs(eIDs) + + return eIDs, nil +} diff --git a/spdxlib/described_elements_test.go b/spdxlib/described_elements_test.go index 8ea9cd5..8b7eb10 100644 --- a/spdxlib/described_elements_test.go +++ b/spdxlib/described_elements_test.go @@ -8,6 +8,7 @@ import ( "github.com/spdx/tools-golang/spdx/common" "github.com/spdx/tools-golang/spdx/v2_1" "github.com/spdx/tools-golang/spdx/v2_2" + "github.com/spdx/tools-golang/spdx/v2_3" ) // ===== 2.1 tests ===== @@ -347,3 +348,172 @@ func Test2_2FailsToGetDescribedPackagesIfNilMap(t *testing.T) { t.Fatalf("expected non-nil error, got nil") } } + +// ===== 2.3 tests ===== + +func Test2_3CanGetIDsOfDescribedPackages(t *testing.T) { + // set up document and some packages and relationships + doc := &v2_3.Document{ + SPDXVersion: "SPDX-2.3", + DataLicense: "CC0-1.0", + SPDXIdentifier: common.ElementID("DOCUMENT"), + CreationInfo: &v2_3.CreationInfo{}, + Packages: []*v2_3.Package{ + {PackageName: "pkg1", PackageSPDXIdentifier: "p1"}, + {PackageName: "pkg2", PackageSPDXIdentifier: "p2"}, + {PackageName: "pkg3", PackageSPDXIdentifier: "p3"}, + {PackageName: "pkg4", PackageSPDXIdentifier: "p4"}, + {PackageName: "pkg5", PackageSPDXIdentifier: "p5"}, + }, + Relationships: []*v2_3.Relationship{ + &v2_3.Relationship{ + RefA: common.MakeDocElementID("", "DOCUMENT"), + RefB: common.MakeDocElementID("", "p1"), + Relationship: "DESCRIBES", + }, + &v2_3.Relationship{ + RefA: common.MakeDocElementID("", "DOCUMENT"), + RefB: common.MakeDocElementID("", "p5"), + Relationship: "DESCRIBES", + }, + // inverse relationship -- should also get detected + &v2_3.Relationship{ + RefA: common.MakeDocElementID("", "p4"), + RefB: common.MakeDocElementID("", "DOCUMENT"), + Relationship: "DESCRIBED_BY", + }, + // different relationship + &v2_3.Relationship{ + RefA: common.MakeDocElementID("", "p1"), + RefB: common.MakeDocElementID("", "p2"), + Relationship: "DEPENDS_ON", + }, + }, + } + + // request IDs for DESCRIBES / DESCRIBED_BY relationships + describedPkgIDs, err := GetDescribedPackageIDs2_3(doc) + if err != nil { + t.Fatalf("expected nil error, got %v", err) + } + // should be three of the five IDs, returned in alphabetical order + if len(describedPkgIDs) != 3 { + t.Fatalf("expected %d packages, got %d", 3, len(describedPkgIDs)) + } + if describedPkgIDs[0] != common.ElementID("p1") { + t.Errorf("expected %v, got %v", common.ElementID("p1"), describedPkgIDs[0]) + } + if describedPkgIDs[1] != common.ElementID("p4") { + t.Errorf("expected %v, got %v", common.ElementID("p4"), describedPkgIDs[1]) + } + if describedPkgIDs[2] != common.ElementID("p5") { + t.Errorf("expected %v, got %v", common.ElementID("p5"), describedPkgIDs[2]) + } +} + +func Test2_3GetDescribedPackagesReturnsSinglePackageIfOnlyOne(t *testing.T) { + // set up document and one package, but no relationships + // b/c only one package + doc := &v2_3.Document{ + SPDXVersion: "SPDX-2.2", + DataLicense: "CC0-1.0", + SPDXIdentifier: common.ElementID("DOCUMENT"), + CreationInfo: &v2_3.CreationInfo{}, + Packages: []*v2_3.Package{ + {PackageName: "pkg1", PackageSPDXIdentifier: "p1"}, + }, + } + + // request IDs for DESCRIBES / DESCRIBED_BY relationships + describedPkgIDs, err := GetDescribedPackageIDs2_3(doc) + if err != nil { + t.Fatalf("expected nil error, got %v", err) + } + // should return the single package + if len(describedPkgIDs) != 1 { + t.Fatalf("expected %d package, got %d", 1, len(describedPkgIDs)) + } + if describedPkgIDs[0] != common.ElementID("p1") { + t.Errorf("expected %v, got %v", common.ElementID("p1"), describedPkgIDs[0]) + } +} + +func Test2_3FailsToGetDescribedPackagesIfMoreThanOneWithoutDescribesRelationship(t *testing.T) { + // set up document and multiple packages, but no DESCRIBES relationships + doc := &v2_3.Document{ + SPDXVersion: "SPDX-2.2", + DataLicense: "CC0-1.0", + SPDXIdentifier: common.ElementID("DOCUMENT"), + CreationInfo: &v2_3.CreationInfo{}, + Packages: []*v2_3.Package{ + {PackageName: "pkg1", PackageSPDXIdentifier: "p1"}, + {PackageName: "pkg2", PackageSPDXIdentifier: "p2"}, + {PackageName: "pkg3", PackageSPDXIdentifier: "p3"}, + {PackageName: "pkg4", PackageSPDXIdentifier: "p4"}, + {PackageName: "pkg5", PackageSPDXIdentifier: "p5"}, + }, + Relationships: []*v2_3.Relationship{ + // different relationship + &v2_3.Relationship{ + RefA: common.MakeDocElementID("", "p1"), + RefB: common.MakeDocElementID("", "p2"), + Relationship: "DEPENDS_ON", + }, + }, + } + + _, err := GetDescribedPackageIDs2_3(doc) + if err == nil { + t.Fatalf("expected non-nil error, got nil") + } +} + +func Test2_3FailsToGetDescribedPackagesIfMoreThanOneWithNilRelationships(t *testing.T) { + // set up document and multiple packages, but no relationships slice + doc := &v2_3.Document{ + SPDXVersion: "SPDX-2.2", + DataLicense: "CC0-1.0", + SPDXIdentifier: common.ElementID("DOCUMENT"), + CreationInfo: &v2_3.CreationInfo{}, + Packages: []*v2_3.Package{ + {PackageName: "pkg1", PackageSPDXIdentifier: "p1"}, + {PackageName: "pkg2", PackageSPDXIdentifier: "p2"}, + }, + } + + _, err := GetDescribedPackageIDs2_3(doc) + if err == nil { + t.Fatalf("expected non-nil error, got nil") + } +} + +func Test2_3FailsToGetDescribedPackagesIfZeroPackagesInMap(t *testing.T) { + // set up document but no packages + doc := &v2_3.Document{ + SPDXVersion: "SPDX-2.2", + DataLicense: "CC0-1.0", + SPDXIdentifier: common.ElementID("DOCUMENT"), + CreationInfo: &v2_3.CreationInfo{}, + Packages: []*v2_3.Package{}, + } + + _, err := GetDescribedPackageIDs2_3(doc) + if err == nil { + t.Fatalf("expected non-nil error, got nil") + } +} + +func Test2_3FailsToGetDescribedPackagesIfNilMap(t *testing.T) { + // set up document but no packages + doc := &v2_3.Document{ + SPDXVersion: "SPDX-2.2", + DataLicense: "CC0-1.0", + SPDXIdentifier: common.ElementID("DOCUMENT"), + CreationInfo: &v2_3.CreationInfo{}, + } + + _, err := GetDescribedPackageIDs2_3(doc) + if err == nil { + t.Fatalf("expected non-nil error, got nil") + } +} diff --git a/spdxlib/documents.go b/spdxlib/documents.go index 8560f08..dc41803 100644 --- a/spdxlib/documents.go +++ b/spdxlib/documents.go @@ -7,6 +7,7 @@ import ( "github.com/spdx/tools-golang/spdx/common" "github.com/spdx/tools-golang/spdx/v2_1" "github.com/spdx/tools-golang/spdx/v2_2" + "github.com/spdx/tools-golang/spdx/v2_3" ) // ValidateDocument2_1 returns an error if the Document is found to be invalid, or nil if the Document is valid. @@ -68,3 +69,33 @@ func ValidateDocument2_2(doc *v2_2.Document) error { return nil } + +// ValidateDocument2_3 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_3(doc *v2_3.Document) error { + // cache a map of package IDs for quick lookups + validElementIDs := make(map[common.ElementID]bool) + for _, docPackage := range doc.Packages { + validElementIDs[docPackage.PackageSPDXIdentifier] = true + } + + for _, unpackagedFile := range doc.Files { + validElementIDs[unpackagedFile.FileSPDXIdentifier] = true + } + + // add the Document element ID + validElementIDs[common.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 index 586cabd..6d04f38 100644 --- a/spdxlib/documents_test.go +++ b/spdxlib/documents_test.go @@ -8,6 +8,7 @@ import ( "github.com/spdx/tools-golang/spdx/common" "github.com/spdx/tools-golang/spdx/v2_1" "github.com/spdx/tools-golang/spdx/v2_2" + "github.com/spdx/tools-golang/spdx/v2_3" ) // ===== 2.1 tests ===== @@ -181,3 +182,89 @@ func Test2_2InvalidDocumentFailsValidation(t *testing.T) { t.Fatalf("expected non-nil error, got nil") } } + +// ===== 2.3 tests ===== + +func Test2_3ValidDocumentPassesValidation(t *testing.T) { + // set up document and some packages and relationships + doc := &v2_3.Document{ + SPDXVersion: "SPDX-2.3", + DataLicense: "CC0-1.0", + SPDXIdentifier: common.ElementID("DOCUMENT"), + CreationInfo: &v2_3.CreationInfo{}, + Packages: []*v2_3.Package{ + {PackageName: "pkg1", PackageSPDXIdentifier: "p1"}, + {PackageName: "pkg2", PackageSPDXIdentifier: "p2"}, + {PackageName: "pkg3", PackageSPDXIdentifier: "p3"}, + {PackageName: "pkg4", PackageSPDXIdentifier: "p4"}, + {PackageName: "pkg5", PackageSPDXIdentifier: "p5"}, + }, + Relationships: []*v2_3.Relationship{ + { + RefA: common.MakeDocElementID("", "DOCUMENT"), + RefB: common.MakeDocElementID("", "p1"), + Relationship: "DESCRIBES", + }, + { + RefA: common.MakeDocElementID("", "DOCUMENT"), + RefB: common.MakeDocElementID("", "p5"), + Relationship: "DESCRIBES", + }, + // inverse relationship -- should also get detected + { + RefA: common.MakeDocElementID("", "p4"), + RefB: common.MakeDocElementID("", "DOCUMENT"), + Relationship: "DESCRIBED_BY", + }, + // different relationship + { + RefA: common.MakeDocElementID("", "p1"), + RefB: common.MakeDocElementID("", "p2"), + Relationship: "DEPENDS_ON", + }, + }, + } + + err := ValidateDocument2_3(doc) + if err != nil { + t.Fatalf("expected nil error, got: %s", err.Error()) + } +} + +func Test2_3InvalidDocumentFailsValidation(t *testing.T) { + // set up document and some packages and relationships + doc := &v2_3.Document{ + SPDXVersion: "SPDX-2.1", + DataLicense: "CC0-1.0", + SPDXIdentifier: common.ElementID("DOCUMENT"), + CreationInfo: &v2_3.CreationInfo{}, + Packages: []*v2_3.Package{ + {PackageName: "pkg1", PackageSPDXIdentifier: "p1"}, + {PackageName: "pkg2", PackageSPDXIdentifier: "p2"}, + {PackageName: "pkg3", PackageSPDXIdentifier: "p3"}, + }, + Relationships: []*v2_3.Relationship{ + { + RefA: common.MakeDocElementID("", "DOCUMENT"), + RefB: common.MakeDocElementID("", "p1"), + Relationship: "DESCRIBES", + }, + { + RefA: common.MakeDocElementID("", "DOCUMENT"), + RefB: common.MakeDocElementID("", "p5"), + Relationship: "DESCRIBES", + }, + // invalid ID p99 + { + RefA: common.MakeDocElementID("", "p1"), + RefB: common.MakeDocElementID("", "p99"), + Relationship: "DEPENDS_ON", + }, + }, + } + + err := ValidateDocument2_3(doc) + if err == nil { + t.Fatalf("expected non-nil error, got nil") + } +} diff --git a/spdxlib/relationships.go b/spdxlib/relationships.go index 5ff4197..aa807d0 100644 --- a/spdxlib/relationships.go +++ b/spdxlib/relationships.go @@ -6,6 +6,7 @@ import ( "github.com/spdx/tools-golang/spdx/common" "github.com/spdx/tools-golang/spdx/v2_1" "github.com/spdx/tools-golang/spdx/v2_2" + "github.com/spdx/tools-golang/spdx/v2_3" ) // FilterRelationships2_1 returns a slice of Element IDs returned by the given filter closure. The closure is passed @@ -35,3 +36,17 @@ func FilterRelationships2_2(doc *v2_2.Document, filter func(*v2_2.Relationship) return elementIDs, nil } + +// FilterRelationships2_3 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_3(doc *v2_3.Document, filter func(*v2_3.Relationship) *common.ElementID) ([]common.ElementID, error) { + elementIDs := []common.ElementID{} + + for _, relationship := range doc.Relationships { + if id := filter(relationship); id != nil { + elementIDs = append(elementIDs, *id) + } + } + + return elementIDs, nil +} -- cgit v1.2.3