aboutsummaryrefslogtreecommitdiff
path: root/spdx/common
diff options
context:
space:
mode:
Diffstat (limited to 'spdx/common')
-rw-r--r--spdx/common/annotation.go44
-rw-r--r--spdx/common/checksum.go34
-rw-r--r--spdx/common/creation_info.go44
-rw-r--r--spdx/common/external.go67
-rw-r--r--spdx/common/identifier.go173
-rw-r--r--spdx/common/identifier_test.go314
-rw-r--r--spdx/common/package.go105
-rw-r--r--spdx/common/snippet.go20
8 files changed, 801 insertions, 0 deletions
diff --git a/spdx/common/annotation.go b/spdx/common/annotation.go
new file mode 100644
index 0000000..e77d7b7
--- /dev/null
+++ b/spdx/common/annotation.go
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+
+package common
+
+import (
+ "encoding/json"
+ "fmt"
+ "strings"
+)
+
+type Annotator struct {
+ Annotator string
+ // including AnnotatorType: one of "Person", "Organization" or "Tool"
+ AnnotatorType string
+}
+
+// UnmarshalJSON takes an annotator in the typical one-line format and parses it into an Annotator struct.
+// This function is also used when unmarshalling YAML
+func (a *Annotator) UnmarshalJSON(data []byte) error {
+ // annotator will simply be a string
+ annotatorStr := string(data)
+ annotatorStr = strings.Trim(annotatorStr, "\"")
+
+ annotatorFields := strings.SplitN(annotatorStr, ": ", 2)
+
+ if len(annotatorFields) != 2 {
+ return fmt.Errorf("failed to parse Annotator '%s'", annotatorStr)
+ }
+
+ a.AnnotatorType = annotatorFields[0]
+ a.Annotator = annotatorFields[1]
+
+ return nil
+}
+
+// MarshalJSON converts the receiver into a slice of bytes representing an Annotator in string form.
+// This function is also used when marshalling to YAML
+func (a Annotator) MarshalJSON() ([]byte, error) {
+ if a.Annotator != "" {
+ return json.Marshal(fmt.Sprintf("%s: %s", a.AnnotatorType, a.Annotator))
+ }
+
+ return []byte{}, nil
+}
diff --git a/spdx/common/checksum.go b/spdx/common/checksum.go
new file mode 100644
index 0000000..aa2ae52
--- /dev/null
+++ b/spdx/common/checksum.go
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+
+package common
+
+// ChecksumAlgorithm represents the algorithm used to generate the file checksum in the Checksum struct.
+type ChecksumAlgorithm string
+
+// The checksum algorithms mentioned in the spdxv2.2.0 https://spdx.github.io/spdx-spec/4-file-information/#44-file-checksum
+const (
+ SHA224 ChecksumAlgorithm = "SHA224"
+ SHA1 ChecksumAlgorithm = "SHA1"
+ SHA256 ChecksumAlgorithm = "SHA256"
+ SHA384 ChecksumAlgorithm = "SHA384"
+ SHA512 ChecksumAlgorithm = "SHA512"
+ MD2 ChecksumAlgorithm = "MD2"
+ MD4 ChecksumAlgorithm = "MD4"
+ MD5 ChecksumAlgorithm = "MD5"
+ MD6 ChecksumAlgorithm = "MD6"
+ SHA3_256 ChecksumAlgorithm = "SHA3-256"
+ SHA3_384 ChecksumAlgorithm = "SHA3-384"
+ SHA3_512 ChecksumAlgorithm = "SHA3-512"
+ BLAKE2b_256 ChecksumAlgorithm = "BLAKE2b-256"
+ BLAKE2b_384 ChecksumAlgorithm = "BLAKE2b-384"
+ BLAKE2b_512 ChecksumAlgorithm = "BLAKE2b-512"
+ BLAKE3 ChecksumAlgorithm = "BLAKE3"
+ ADLER32 ChecksumAlgorithm = "ADLER32"
+)
+
+// Checksum provides a unique identifier to match analysis information on each specific file in a package.
+// The Algorithm field describes the ChecksumAlgorithm used and the Value represents the file checksum
+type Checksum struct {
+ Algorithm ChecksumAlgorithm `json:"algorithm"`
+ Value string `json:"checksumValue"`
+}
diff --git a/spdx/common/creation_info.go b/spdx/common/creation_info.go
new file mode 100644
index 0000000..c87ae7b
--- /dev/null
+++ b/spdx/common/creation_info.go
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+
+package common
+
+import (
+ "encoding/json"
+ "fmt"
+ "strings"
+)
+
+// Creator is a wrapper around the Creator SPDX field. The SPDX field contains two values, which requires special
+// handling in order to marshal/unmarshal it to/from Go data types.
+type Creator struct {
+ Creator string
+ // CreatorType should be one of "Person", "Organization", or "Tool"
+ CreatorType string
+}
+
+// UnmarshalJSON takes an annotator in the typical one-line format and parses it into a Creator struct.
+// This function is also used when unmarshalling YAML
+func (c *Creator) UnmarshalJSON(data []byte) error {
+ str := string(data)
+ str = strings.Trim(str, "\"")
+ fields := strings.SplitN(str, ": ", 2)
+
+ if len(fields) != 2 {
+ return fmt.Errorf("failed to parse Creator '%s'", str)
+ }
+
+ c.CreatorType = fields[0]
+ c.Creator = fields[1]
+
+ return nil
+}
+
+// MarshalJSON converts the receiver into a slice of bytes representing a Creator in string form.
+// This function is also used with marshalling to YAML
+func (c Creator) MarshalJSON() ([]byte, error) {
+ if c.Creator != "" {
+ return json.Marshal(fmt.Sprintf("%s: %s", c.CreatorType, c.Creator))
+ }
+
+ return []byte{}, nil
+}
diff --git a/spdx/common/external.go b/spdx/common/external.go
new file mode 100644
index 0000000..59c3f0f
--- /dev/null
+++ b/spdx/common/external.go
@@ -0,0 +1,67 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+
+package common
+
+// Constants for various string types
+const (
+ // F.2 Security types
+ TypeSecurityCPE23Type string = "cpe23Type"
+ TypeSecurityCPE22Type string = "cpe22Type"
+ TypeSecurityAdvisory string = "advisory"
+ TypeSecurityFix string = "fix"
+ TypeSecurityUrl string = "url"
+ TypeSecuritySwid string = "swid"
+
+ // F.3 Package-Manager types
+ TypePackageManagerMavenCentral string = "maven-central"
+ TypePackageManagerNpm string = "npm"
+ TypePackageManagerNuGet string = "nuget"
+ TypePackageManagerBower string = "bower"
+ TypePackageManagerPURL string = "purl"
+
+ // 11.1 Relationship field types
+ TypeRelationshipDescribe string = "DESCRIBES"
+ TypeRelationshipDescribeBy string = "DESCRIBED_BY"
+ TypeRelationshipContains string = "CONTAINS"
+ TypeRelationshipContainedBy string = "CONTAINED_BY"
+ TypeRelationshipDependsOn string = "DEPENDS_ON"
+ TypeRelationshipDependencyOf string = "DEPENDENCY_OF"
+ TypeRelationshipBuildDependencyOf string = "BUILD_DEPENDENCY_OF"
+ TypeRelationshipDevDependencyOf string = "DEV_DEPENDENCY_OF"
+ TypeRelationshipOptionalDependencyOf string = "OPTIONAL_DEPENDENCY_OF"
+ TypeRelationshipProvidedDependencyOf string = "PROVIDED_DEPENDENCY_OF"
+ TypeRelationshipTestDependencyOf string = "TEST_DEPENDENCY_OF"
+ TypeRelationshipRuntimeDependencyOf string = "RUNTIME_DEPENDENCY_OF"
+ TypeRelationshipExampleOf string = "EXAMPLE_OF"
+ TypeRelationshipGenerates string = "GENERATES"
+ TypeRelationshipGeneratedFrom string = "GENERATED_FROM"
+ TypeRelationshipAncestorOf string = "ANCESTOR_OF"
+ TypeRelationshipDescendantOf string = "DESCENDANT_OF"
+ TypeRelationshipVariantOf string = "VARIANT_OF"
+ TypeRelationshipDistributionArtifact string = "DISTRIBUTION_ARTIFACT"
+ TypeRelationshipPatchFor string = "PATCH_FOR"
+ TypeRelationshipPatchApplied string = "PATCH_APPLIED"
+ TypeRelationshipCopyOf string = "COPY_OF"
+ TypeRelationshipFileAdded string = "FILE_ADDED"
+ TypeRelationshipFileDeleted string = "FILE_DELETED"
+ TypeRelationshipFileModified string = "FILE_MODIFIED"
+ TypeRelationshipExpandedFromArchive string = "EXPANDED_FROM_ARCHIVE"
+ TypeRelationshipDynamicLink string = "DYNAMIC_LINK"
+ TypeRelationshipStaticLink string = "STATIC_LINK"
+ TypeRelationshipDataFileOf string = "DATA_FILE_OF"
+ TypeRelationshipTestCaseOf string = "TEST_CASE_OF"
+ TypeRelationshipBuildToolOf string = "BUILD_TOOL_OF"
+ TypeRelationshipDevToolOf string = "DEV_TOOL_OF"
+ TypeRelationshipTestOf string = "TEST_OF"
+ TypeRelationshipTestToolOf string = "TEST_TOOL_OF"
+ TypeRelationshipDocumentationOf string = "DOCUMENTATION_OF"
+ TypeRelationshipOptionalComponentOf string = "OPTIONAL_COMPONENT_OF"
+ TypeRelationshipMetafileOf string = "METAFILE_OF"
+ TypeRelationshipPackageOf string = "PACKAGE_OF"
+ TypeRelationshipAmends string = "AMENDS"
+ TypeRelationshipPrerequisiteFor string = "PREREQUISITE_FOR"
+ TypeRelationshipHasPrerequisite string = "HAS_PREREQUISITE"
+ TypeRelationshipRequirementDescriptionFor string = "REQUIREMENT_DESCRIPTION_FOR"
+ TypeRelationshipSpecificationFor string = "SPECIFICATION_FOR"
+ TypeRelationshipOther string = "OTHER"
+)
diff --git a/spdx/common/identifier.go b/spdx/common/identifier.go
new file mode 100644
index 0000000..806a815
--- /dev/null
+++ b/spdx/common/identifier.go
@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+
+package common
+
+import (
+ "encoding/json"
+ "fmt"
+ "strings"
+)
+
+const (
+ spdxRefPrefix = "SPDXRef-"
+ documentRefPrefix = "DocumentRef-"
+)
+
+// ElementID represents the identifier string portion of an SPDX element
+// identifier. DocElementID should be used for any attributes which can
+// contain identifiers defined in a different SPDX document.
+// ElementIDs should NOT contain the mandatory 'SPDXRef-' portion.
+type ElementID string
+
+// MarshalJSON returns an SPDXRef- prefixed JSON string
+func (d ElementID) MarshalJSON() ([]byte, error) {
+ return json.Marshal(prefixElementId(d))
+}
+
+// UnmarshalJSON validates SPDXRef- prefixes and removes them when processing ElementIDs
+func (d *ElementID) UnmarshalJSON(data []byte) error {
+ // SPDX identifier will simply be a string
+ idStr := string(data)
+ idStr = strings.Trim(idStr, "\"")
+
+ e, err := trimElementIdPrefix(idStr)
+ if err != nil {
+ return err
+ }
+ *d = e
+ return nil
+}
+
+// prefixElementId adds the SPDXRef- prefix to an element ID if it does not have one
+func prefixElementId(id ElementID) string {
+ val := string(id)
+ if !strings.HasPrefix(val, spdxRefPrefix) {
+ return spdxRefPrefix + val
+ }
+ return val
+}
+
+// trimElementIdPrefix removes the SPDXRef- prefix from an element ID string or returns an error if it
+// does not start with SPDXRef-
+func trimElementIdPrefix(id string) (ElementID, error) {
+ // handle SPDXRef-
+ idFields := strings.SplitN(id, spdxRefPrefix, 2)
+ if len(idFields) != 2 {
+ return "", fmt.Errorf("failed to parse SPDX identifier '%s'", id)
+ }
+
+ e := ElementID(idFields[1])
+ return e, nil
+}
+
+// DocElementID represents an SPDX element identifier that could be defined
+// in a different SPDX document, and therefore could have a "DocumentRef-"
+// portion, such as Relationships and Annotations.
+// ElementID is used for attributes in which a "DocumentRef-" portion cannot
+// appear, such as a Package or File definition (since it is necessarily
+// being defined in the present document).
+// DocumentRefID will be the empty string for elements defined in the
+// present document.
+// DocElementIDs should NOT contain the mandatory 'DocumentRef-' or
+// 'SPDXRef-' portions.
+// SpecialID is used ONLY if the DocElementID matches a defined set of
+// permitted special values for a particular field, e.g. "NONE" or
+// "NOASSERTION" for the right-hand side of Relationships. If SpecialID
+// is set, DocumentRefID and ElementRefID should be empty (and vice versa).
+type DocElementID struct {
+ DocumentRefID string
+ ElementRefID ElementID
+ SpecialID string
+}
+
+// MarshalJSON converts the receiver into a slice of bytes representing a DocElementID in string form.
+// This function is also used when marshalling to YAML
+func (d DocElementID) MarshalJSON() ([]byte, error) {
+ if d.DocumentRefID != "" && d.ElementRefID != "" {
+ idStr := prefixElementId(d.ElementRefID)
+ return json.Marshal(fmt.Sprintf("%s%s:%s", documentRefPrefix, d.DocumentRefID, idStr))
+ } else if d.ElementRefID != "" {
+ return json.Marshal(prefixElementId(d.ElementRefID))
+ } else if d.SpecialID != "" {
+ return json.Marshal(d.SpecialID)
+ }
+
+ return []byte{}, fmt.Errorf("failed to marshal empty DocElementID")
+}
+
+// UnmarshalJSON takes a SPDX Identifier string parses it into a DocElementID struct.
+// This function is also used when unmarshalling YAML
+func (d *DocElementID) UnmarshalJSON(data []byte) (err error) {
+ // SPDX identifier will simply be a string
+ idStr := string(data)
+ idStr = strings.Trim(idStr, "\"")
+
+ // handle special cases
+ if idStr == "NONE" || idStr == "NOASSERTION" {
+ d.SpecialID = idStr
+ return nil
+ }
+
+ var idFields []string
+ // handle DocumentRef- if present
+ if strings.HasPrefix(idStr, documentRefPrefix) {
+ // strip out the "DocumentRef-" so we can get the value
+ idFields = strings.SplitN(idStr, documentRefPrefix, 2)
+ idStr = idFields[1]
+
+ // an SPDXRef can appear after a DocumentRef, separated by a colon
+ idFields = strings.SplitN(idStr, ":", 2)
+ d.DocumentRefID = idFields[0]
+
+ if len(idFields) == 2 {
+ idStr = idFields[1]
+ } else {
+ return nil
+ }
+ }
+
+ d.ElementRefID, err = trimElementIdPrefix(idStr)
+ return err
+}
+
+// TODO: add equivalents for LicenseRef- identifiers
+
+// MakeDocElementID takes strings (without prefixes) for the DocumentRef-
+// and SPDXRef- identifiers, and returns a DocElementID. An empty string
+// should be used for the DocumentRef- portion if it is referring to the
+// present document.
+func MakeDocElementID(docRef string, eltRef string) DocElementID {
+ return DocElementID{
+ DocumentRefID: docRef,
+ ElementRefID: ElementID(eltRef),
+ }
+}
+
+// MakeDocElementSpecial takes a "special" string (e.g. "NONE" or
+// "NOASSERTION" for the right side of a Relationship), nd returns
+// a DocElementID with it in the SpecialID field. Other fields will
+// be empty.
+func MakeDocElementSpecial(specialID string) DocElementID {
+ return DocElementID{SpecialID: specialID}
+}
+
+// RenderElementID takes an ElementID and returns the string equivalent,
+// with the SPDXRef- prefix reinserted.
+func RenderElementID(eID ElementID) string {
+ return spdxRefPrefix + string(eID)
+}
+
+// RenderDocElementID takes a DocElementID and returns the string equivalent,
+// with the SPDXRef- prefix (and, if applicable, the DocumentRef- prefix)
+// reinserted. If a SpecialID is present, it will be rendered verbatim and
+// DocumentRefID and ElementRefID will be ignored.
+func RenderDocElementID(deID DocElementID) string {
+ if deID.SpecialID != "" {
+ return deID.SpecialID
+ }
+ prefix := ""
+ if deID.DocumentRefID != "" {
+ prefix = documentRefPrefix + deID.DocumentRefID + ":"
+ }
+ return prefix + spdxRefPrefix + string(deID.ElementRefID)
+}
diff --git a/spdx/common/identifier_test.go b/spdx/common/identifier_test.go
new file mode 100644
index 0000000..8df2ea3
--- /dev/null
+++ b/spdx/common/identifier_test.go
@@ -0,0 +1,314 @@
+package common
+
+import (
+ "encoding/json"
+ "fmt"
+ "reflect"
+ "strings"
+ "testing"
+)
+
+func Test_DocElementIDEncoding(t *testing.T) {
+ tests := []struct {
+ name string
+ value DocElementID
+ expected string
+ err bool
+ }{
+ {
+ name: "ElementRefID",
+ value: DocElementID{
+ ElementRefID: "some-id",
+ },
+ expected: "SPDXRef-some-id",
+ },
+ {
+ name: "DocumentRefID:ElementRefID",
+ value: DocElementID{
+ DocumentRefID: "a-doc",
+ ElementRefID: "some-id",
+ },
+ expected: "DocumentRef-a-doc:SPDXRef-some-id",
+ },
+ {
+ name: "DocumentRefID no ElementRefID",
+ value: DocElementID{
+ DocumentRefID: "a-doc",
+ },
+ err: true,
+ },
+ {
+ name: "SpecialID",
+ value: DocElementID{
+ SpecialID: "special-id",
+ },
+ expected: "special-id",
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ result, err := json.Marshal(test.value)
+ switch {
+ case !test.err && err != nil:
+ t.Fatalf("unexpected error: %v", err)
+ case test.err && err == nil:
+ t.Fatalf("expected error but got none")
+ case test.err:
+ return
+ }
+ s := string(result)
+ if !strings.HasPrefix(s, `"`) || !strings.HasSuffix(s, `"`) {
+ t.Fatalf("string was not returned: %s", s)
+ }
+ s = strings.Trim(s, `"`)
+ if test.expected != s {
+ t.Fatalf("%s != %s", test.expected, s)
+ }
+ })
+ }
+}
+
+func Test_DocElementIDDecoding(t *testing.T) {
+ tests := []struct {
+ name string
+ value string
+ expected DocElementID
+ err bool
+ }{
+ {
+ name: "ElementRefID",
+ value: "SPDXRef-some-id",
+ expected: DocElementID{
+ ElementRefID: "some-id",
+ },
+ },
+ {
+ name: "DocumentRefID:ElementRefID",
+ value: "DocumentRef-a-doc:SPDXRef-some-id",
+ expected: DocElementID{
+ DocumentRefID: "a-doc",
+ ElementRefID: "some-id",
+ },
+ },
+ {
+ name: "DocumentRefID no ElementRefID",
+ value: "DocumentRef-a-doc",
+ expected: DocElementID{
+ DocumentRefID: "a-doc",
+ },
+ },
+ {
+ name: "DocumentRefID invalid ElementRefID",
+ value: "DocumentRef-a-doc:invalid",
+ err: true,
+ },
+ {
+ name: "invalid format",
+ value: "some-id-without-spdxref",
+ err: true,
+ },
+ {
+ name: "SpecialID NONE",
+ value: "NONE",
+ expected: DocElementID{
+ SpecialID: "NONE",
+ },
+ },
+ {
+ name: "SpecialID NOASSERTION",
+ value: "NOASSERTION",
+ expected: DocElementID{
+ SpecialID: "NOASSERTION",
+ },
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ out := DocElementID{}
+ s := fmt.Sprintf(`"%s"`, test.value)
+ err := json.Unmarshal([]byte(s), &out)
+ switch {
+ case !test.err && err != nil:
+ t.Fatalf("unexpected error: %v", err)
+ case test.err && err == nil:
+ t.Fatalf("expected error but got none")
+ case test.err:
+ return
+ }
+ if !reflect.DeepEqual(test.expected, out) {
+ t.Fatalf("unexpected value: %v != %v", test.expected, out)
+ }
+ })
+ }
+}
+
+func Test_ElementIDEncoding(t *testing.T) {
+ tests := []struct {
+ name string
+ value ElementID
+ expected string
+ err bool
+ }{
+ {
+ name: "appends spdxref",
+ value: ElementID("some-id"),
+ expected: "SPDXRef-some-id",
+ },
+ {
+ name: "appends spdxref",
+ value: ElementID("SPDXRef-some-id"),
+ expected: "SPDXRef-some-id",
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ result, err := json.Marshal(test.value)
+ switch {
+ case !test.err && err != nil:
+ t.Fatalf("unexpected error: %v", err)
+ case test.err && err == nil:
+ t.Fatalf("expected error but got none")
+ case test.err:
+ return
+ }
+ s := string(result)
+ if !strings.HasPrefix(s, `"`) || !strings.HasSuffix(s, `"`) {
+ t.Fatalf("string was not returned: %s", s)
+ }
+ s = strings.Trim(s, `"`)
+ if test.expected != s {
+ t.Fatalf("%s != %s", test.expected, s)
+ }
+ })
+ }
+}
+
+func Test_ElementIDDecoding(t *testing.T) {
+ tests := []struct {
+ name string
+ value string
+ expected ElementID
+ err bool
+ }{
+ {
+ name: "valid id",
+ value: "SPDXRef-some-id",
+ expected: ElementID("some-id"),
+ },
+ {
+ name: "invalid format",
+ value: "some-id-without-spdxref",
+ err: true,
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ var out ElementID
+ s := fmt.Sprintf(`"%s"`, test.value)
+ err := json.Unmarshal([]byte(s), &out)
+ switch {
+ case !test.err && err != nil:
+ t.Fatalf("unexpected error: %v", err)
+ case test.err && err == nil:
+ t.Fatalf("expected error but got none")
+ case test.err:
+ return
+ }
+ if !reflect.DeepEqual(test.expected, out) {
+ t.Fatalf("unexpected value: %v != %v", test.expected, out)
+ }
+ })
+ }
+}
+
+func Test_ElementIDStructEncoding(t *testing.T) {
+ type typ struct {
+ Id ElementID `json:"id"`
+ }
+ tests := []struct {
+ name string
+ value typ
+ expected string
+ err bool
+ }{
+ {
+ name: "appends spdxref",
+ value: typ{
+ Id: ElementID("some-id"),
+ },
+ expected: `{"id":"SPDXRef-some-id"}`,
+ },
+ {
+ name: "appends spdxref",
+ value: typ{
+ Id: ElementID("SPDXRef-some-id"),
+ },
+ expected: `{"id":"SPDXRef-some-id"}`,
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ result, err := json.Marshal(test.value)
+ switch {
+ case !test.err && err != nil:
+ t.Fatalf("unexpected error: %v", err)
+ case test.err && err == nil:
+ t.Fatalf("expected error but got none")
+ case test.err:
+ return
+ }
+ s := string(result)
+ if test.expected != s {
+ t.Fatalf("%s != %s", test.expected, s)
+ }
+ })
+ }
+}
+
+func Test_ElementIDStructDecoding(t *testing.T) {
+ type typ struct {
+ Id ElementID `json:"id"`
+ }
+ tests := []struct {
+ name string
+ value string
+ expected typ
+ err bool
+ }{
+ {
+ name: "valid id",
+ expected: typ{
+ Id: ElementID("some-id"),
+ },
+ value: `{"id":"SPDXRef-some-id"}`,
+ },
+ {
+ name: "invalid format",
+ value: `{"id":"some-id"}`,
+ err: true,
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ out := typ{}
+ err := json.Unmarshal([]byte(test.value), &out)
+ switch {
+ case !test.err && err != nil:
+ t.Fatalf("unexpected error: %v", err)
+ case test.err && err == nil:
+ t.Fatalf("expected error but got none")
+ case test.err:
+ return
+ }
+ if !reflect.DeepEqual(test.expected, out) {
+ t.Fatalf("unexpected value: %v != %v", test.expected, out)
+ }
+ })
+ }
+}
diff --git a/spdx/common/package.go b/spdx/common/package.go
new file mode 100644
index 0000000..de5a075
--- /dev/null
+++ b/spdx/common/package.go
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+
+package common
+
+import (
+ "encoding/json"
+ "fmt"
+ "strings"
+)
+
+type Supplier struct {
+ // can be "NOASSERTION"
+ Supplier string
+ // SupplierType can be one of "Person", "Organization", or empty if Supplier is "NOASSERTION"
+ SupplierType string
+}
+
+// UnmarshalJSON takes a supplier in the typical one-line format and parses it into a Supplier struct.
+// This function is also used when unmarshalling YAML
+func (s *Supplier) UnmarshalJSON(data []byte) error {
+ // the value is just a string presented as a slice of bytes
+ supplierStr := string(data)
+ supplierStr = strings.Trim(supplierStr, "\"")
+
+ if supplierStr == "NOASSERTION" {
+ s.Supplier = supplierStr
+ return nil
+ }
+
+ supplierFields := strings.SplitN(supplierStr, ": ", 2)
+
+ if len(supplierFields) != 2 {
+ return fmt.Errorf("failed to parse Supplier '%s'", supplierStr)
+ }
+
+ s.SupplierType = supplierFields[0]
+ s.Supplier = supplierFields[1]
+
+ return nil
+}
+
+// MarshalJSON converts the receiver into a slice of bytes representing a Supplier in string form.
+// This function is also used when marshalling to YAML
+func (s Supplier) MarshalJSON() ([]byte, error) {
+ if s.Supplier == "NOASSERTION" {
+ return json.Marshal(s.Supplier)
+ } else if s.SupplierType != "" && s.Supplier != "" {
+ return json.Marshal(fmt.Sprintf("%s: %s", s.SupplierType, s.Supplier))
+ }
+
+ return []byte{}, fmt.Errorf("failed to marshal invalid Supplier: %+v", s)
+}
+
+type Originator struct {
+ // can be "NOASSERTION"
+ Originator string
+ // OriginatorType can be one of "Person", "Organization", or empty if Originator is "NOASSERTION"
+ OriginatorType string
+}
+
+// UnmarshalJSON takes an originator in the typical one-line format and parses it into an Originator struct.
+// This function is also used when unmarshalling YAML
+func (o *Originator) UnmarshalJSON(data []byte) error {
+ // the value is just a string presented as a slice of bytes
+ originatorStr := string(data)
+ originatorStr = strings.Trim(originatorStr, "\"")
+
+ if originatorStr == "NOASSERTION" {
+ o.Originator = originatorStr
+ return nil
+ }
+
+ originatorFields := strings.SplitN(originatorStr, ": ", 2)
+
+ if len(originatorFields) != 2 {
+ return fmt.Errorf("failed to parse Originator '%s'", originatorStr)
+ }
+
+ o.OriginatorType = originatorFields[0]
+ o.Originator = originatorFields[1]
+
+ return nil
+}
+
+// MarshalJSON converts the receiver into a slice of bytes representing an Originator in string form.
+// This function is also used when marshalling to YAML
+func (o Originator) MarshalJSON() ([]byte, error) {
+ if o.Originator == "NOASSERTION" {
+ return json.Marshal(o.Originator)
+ } else if o.Originator != "" {
+ return json.Marshal(fmt.Sprintf("%s: %s", o.OriginatorType, o.Originator))
+ }
+
+ return []byte{}, nil
+}
+
+type PackageVerificationCode struct {
+ // Cardinality: mandatory, one if filesAnalyzed is true / omitted;
+ // zero (must be omitted) if filesAnalyzed is false
+ Value string `json:"packageVerificationCodeValue"`
+ // Spec also allows specifying files to exclude from the
+ // verification code algorithm; intended to enable exclusion of
+ // the SPDX document file itself.
+ ExcludedFiles []string `json:"packageVerificationCodeExcludedFiles,omitempty"`
+}
diff --git a/spdx/common/snippet.go b/spdx/common/snippet.go
new file mode 100644
index 0000000..63afac3
--- /dev/null
+++ b/spdx/common/snippet.go
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+
+package common
+
+type SnippetRangePointer struct {
+ // 5.3: Snippet Byte Range: [start byte]:[end byte]
+ // Cardinality: mandatory, one
+ Offset int `json:"offset,omitempty"`
+
+ // 5.4: Snippet Line Range: [start line]:[end line]
+ // Cardinality: optional, one
+ LineNumber int `json:"lineNumber,omitempty"`
+
+ FileSPDXIdentifier ElementID `json:"reference"`
+}
+
+type SnippetRange struct {
+ StartPointer SnippetRangePointer `json:"startPointer"`
+ EndPointer SnippetRangePointer `json:"endPointer"`
+}