xref: /aosp_15_r20/external/spdx-tools/spdx/common/identifier.go (revision ba677afa8f67bb56cbc794f4d0e378e0da058e16)
1*ba677afaSXin Li// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
2*ba677afaSXin Li
3*ba677afaSXin Lipackage common
4*ba677afaSXin Li
5*ba677afaSXin Liimport (
6*ba677afaSXin Li	"encoding/json"
7*ba677afaSXin Li	"fmt"
8*ba677afaSXin Li	"strings"
9*ba677afaSXin Li)
10*ba677afaSXin Li
11*ba677afaSXin Liconst (
12*ba677afaSXin Li	spdxRefPrefix     = "SPDXRef-"
13*ba677afaSXin Li	documentRefPrefix = "DocumentRef-"
14*ba677afaSXin Li)
15*ba677afaSXin Li
16*ba677afaSXin Li// ElementID represents the identifier string portion of an SPDX element
17*ba677afaSXin Li// identifier. DocElementID should be used for any attributes which can
18*ba677afaSXin Li// contain identifiers defined in a different SPDX document.
19*ba677afaSXin Li// ElementIDs should NOT contain the mandatory 'SPDXRef-' portion.
20*ba677afaSXin Litype ElementID string
21*ba677afaSXin Li
22*ba677afaSXin Li// MarshalJSON returns an SPDXRef- prefixed JSON string
23*ba677afaSXin Lifunc (d ElementID) MarshalJSON() ([]byte, error) {
24*ba677afaSXin Li	return json.Marshal(prefixElementId(d))
25*ba677afaSXin Li}
26*ba677afaSXin Li
27*ba677afaSXin Li// UnmarshalJSON validates SPDXRef- prefixes and removes them when processing ElementIDs
28*ba677afaSXin Lifunc (d *ElementID) UnmarshalJSON(data []byte) error {
29*ba677afaSXin Li	// SPDX identifier will simply be a string
30*ba677afaSXin Li	idStr := string(data)
31*ba677afaSXin Li	idStr = strings.Trim(idStr, "\"")
32*ba677afaSXin Li
33*ba677afaSXin Li	e, err := trimElementIdPrefix(idStr)
34*ba677afaSXin Li	if err != nil {
35*ba677afaSXin Li		return err
36*ba677afaSXin Li	}
37*ba677afaSXin Li	*d = e
38*ba677afaSXin Li	return nil
39*ba677afaSXin Li}
40*ba677afaSXin Li
41*ba677afaSXin Li// prefixElementId adds the SPDXRef- prefix to an element ID if it does not have one
42*ba677afaSXin Lifunc prefixElementId(id ElementID) string {
43*ba677afaSXin Li	val := string(id)
44*ba677afaSXin Li	if !strings.HasPrefix(val, spdxRefPrefix) {
45*ba677afaSXin Li		return spdxRefPrefix + val
46*ba677afaSXin Li	}
47*ba677afaSXin Li	return val
48*ba677afaSXin Li}
49*ba677afaSXin Li
50*ba677afaSXin Li// trimElementIdPrefix removes the SPDXRef- prefix from an element ID string or returns an error if it
51*ba677afaSXin Li// does not start with SPDXRef-
52*ba677afaSXin Lifunc trimElementIdPrefix(id string) (ElementID, error) {
53*ba677afaSXin Li	// handle SPDXRef-
54*ba677afaSXin Li	idFields := strings.SplitN(id, spdxRefPrefix, 2)
55*ba677afaSXin Li	if len(idFields) != 2 {
56*ba677afaSXin Li		return "", fmt.Errorf("failed to parse SPDX identifier '%s'", id)
57*ba677afaSXin Li	}
58*ba677afaSXin Li
59*ba677afaSXin Li	e := ElementID(idFields[1])
60*ba677afaSXin Li	return e, nil
61*ba677afaSXin Li}
62*ba677afaSXin Li
63*ba677afaSXin Li// DocElementID represents an SPDX element identifier that could be defined
64*ba677afaSXin Li// in a different SPDX document, and therefore could have a "DocumentRef-"
65*ba677afaSXin Li// portion, such as Relationships and Annotations.
66*ba677afaSXin Li// ElementID is used for attributes in which a "DocumentRef-" portion cannot
67*ba677afaSXin Li// appear, such as a Package or File definition (since it is necessarily
68*ba677afaSXin Li// being defined in the present document).
69*ba677afaSXin Li// DocumentRefID will be the empty string for elements defined in the
70*ba677afaSXin Li// present document.
71*ba677afaSXin Li// DocElementIDs should NOT contain the mandatory 'DocumentRef-' or
72*ba677afaSXin Li// 'SPDXRef-' portions.
73*ba677afaSXin Li// SpecialID is used ONLY if the DocElementID matches a defined set of
74*ba677afaSXin Li// permitted special values for a particular field, e.g. "NONE" or
75*ba677afaSXin Li// "NOASSERTION" for the right-hand side of Relationships. If SpecialID
76*ba677afaSXin Li// is set, DocumentRefID and ElementRefID should be empty (and vice versa).
77*ba677afaSXin Litype DocElementID struct {
78*ba677afaSXin Li	DocumentRefID string
79*ba677afaSXin Li	ElementRefID  ElementID
80*ba677afaSXin Li	SpecialID     string
81*ba677afaSXin Li}
82*ba677afaSXin Li
83*ba677afaSXin Li// MarshalJSON converts the receiver into a slice of bytes representing a DocElementID in string form.
84*ba677afaSXin Li// This function is also used when marshalling to YAML
85*ba677afaSXin Lifunc (d DocElementID) MarshalJSON() ([]byte, error) {
86*ba677afaSXin Li	if d.DocumentRefID != "" && d.ElementRefID != "" {
87*ba677afaSXin Li		idStr := prefixElementId(d.ElementRefID)
88*ba677afaSXin Li		return json.Marshal(fmt.Sprintf("%s%s:%s", documentRefPrefix, d.DocumentRefID, idStr))
89*ba677afaSXin Li	} else if d.ElementRefID != "" {
90*ba677afaSXin Li		return json.Marshal(prefixElementId(d.ElementRefID))
91*ba677afaSXin Li	} else if d.SpecialID != "" {
92*ba677afaSXin Li		return json.Marshal(d.SpecialID)
93*ba677afaSXin Li	}
94*ba677afaSXin Li
95*ba677afaSXin Li	return []byte{}, fmt.Errorf("failed to marshal empty DocElementID")
96*ba677afaSXin Li}
97*ba677afaSXin Li
98*ba677afaSXin Li// UnmarshalJSON takes a SPDX Identifier string parses it into a DocElementID struct.
99*ba677afaSXin Li// This function is also used when unmarshalling YAML
100*ba677afaSXin Lifunc (d *DocElementID) UnmarshalJSON(data []byte) (err error) {
101*ba677afaSXin Li	// SPDX identifier will simply be a string
102*ba677afaSXin Li	idStr := string(data)
103*ba677afaSXin Li	idStr = strings.Trim(idStr, "\"")
104*ba677afaSXin Li
105*ba677afaSXin Li	// handle special cases
106*ba677afaSXin Li	if idStr == "NONE" || idStr == "NOASSERTION" {
107*ba677afaSXin Li		d.SpecialID = idStr
108*ba677afaSXin Li		return nil
109*ba677afaSXin Li	}
110*ba677afaSXin Li
111*ba677afaSXin Li	var idFields []string
112*ba677afaSXin Li	// handle DocumentRef- if present
113*ba677afaSXin Li	if strings.HasPrefix(idStr, documentRefPrefix) {
114*ba677afaSXin Li		// strip out the "DocumentRef-" so we can get the value
115*ba677afaSXin Li		idFields = strings.SplitN(idStr, documentRefPrefix, 2)
116*ba677afaSXin Li		idStr = idFields[1]
117*ba677afaSXin Li
118*ba677afaSXin Li		// an SPDXRef can appear after a DocumentRef, separated by a colon
119*ba677afaSXin Li		idFields = strings.SplitN(idStr, ":", 2)
120*ba677afaSXin Li		d.DocumentRefID = idFields[0]
121*ba677afaSXin Li
122*ba677afaSXin Li		if len(idFields) == 2 {
123*ba677afaSXin Li			idStr = idFields[1]
124*ba677afaSXin Li		} else {
125*ba677afaSXin Li			return nil
126*ba677afaSXin Li		}
127*ba677afaSXin Li	}
128*ba677afaSXin Li
129*ba677afaSXin Li	d.ElementRefID, err = trimElementIdPrefix(idStr)
130*ba677afaSXin Li	return err
131*ba677afaSXin Li}
132*ba677afaSXin Li
133*ba677afaSXin Li// TODO: add equivalents for LicenseRef- identifiers
134*ba677afaSXin Li
135*ba677afaSXin Li// MakeDocElementID takes strings (without prefixes) for the DocumentRef-
136*ba677afaSXin Li// and SPDXRef- identifiers, and returns a DocElementID. An empty string
137*ba677afaSXin Li// should be used for the DocumentRef- portion if it is referring to the
138*ba677afaSXin Li// present document.
139*ba677afaSXin Lifunc MakeDocElementID(docRef string, eltRef string) DocElementID {
140*ba677afaSXin Li	return DocElementID{
141*ba677afaSXin Li		DocumentRefID: docRef,
142*ba677afaSXin Li		ElementRefID:  ElementID(eltRef),
143*ba677afaSXin Li	}
144*ba677afaSXin Li}
145*ba677afaSXin Li
146*ba677afaSXin Li// MakeDocElementSpecial takes a "special" string (e.g. "NONE" or
147*ba677afaSXin Li// "NOASSERTION" for the right side of a Relationship), nd returns
148*ba677afaSXin Li// a DocElementID with it in the SpecialID field. Other fields will
149*ba677afaSXin Li// be empty.
150*ba677afaSXin Lifunc MakeDocElementSpecial(specialID string) DocElementID {
151*ba677afaSXin Li	return DocElementID{SpecialID: specialID}
152*ba677afaSXin Li}
153*ba677afaSXin Li
154*ba677afaSXin Li// RenderElementID takes an ElementID and returns the string equivalent,
155*ba677afaSXin Li// with the SPDXRef- prefix reinserted.
156*ba677afaSXin Lifunc RenderElementID(eID ElementID) string {
157*ba677afaSXin Li	return spdxRefPrefix + string(eID)
158*ba677afaSXin Li}
159*ba677afaSXin Li
160*ba677afaSXin Li// RenderDocElementID takes a DocElementID and returns the string equivalent,
161*ba677afaSXin Li// with the SPDXRef- prefix (and, if applicable, the DocumentRef- prefix)
162*ba677afaSXin Li// reinserted. If a SpecialID is present, it will be rendered verbatim and
163*ba677afaSXin Li// DocumentRefID and ElementRefID will be ignored.
164*ba677afaSXin Lifunc RenderDocElementID(deID DocElementID) string {
165*ba677afaSXin Li	if deID.SpecialID != "" {
166*ba677afaSXin Li		return deID.SpecialID
167*ba677afaSXin Li	}
168*ba677afaSXin Li	prefix := ""
169*ba677afaSXin Li	if deID.DocumentRefID != "" {
170*ba677afaSXin Li		prefix = documentRefPrefix + deID.DocumentRefID + ":"
171*ba677afaSXin Li	}
172*ba677afaSXin Li	return prefix + spdxRefPrefix + string(deID.ElementRefID)
173*ba677afaSXin Li}
174