diff --git a/cyclonedx.go b/cyclonedx.go index 5eb9393..69c2b18 100644 --- a/cyclonedx.go +++ b/cyclonedx.go @@ -18,11 +18,7 @@ package cyclonedx import ( - "encoding/json" "encoding/xml" - "errors" - "fmt" - "io" "regexp" ) @@ -98,24 +94,6 @@ func Bool(value bool) *bool { type BOMReference string -// bomReferenceXML is temporarily used for marshalling and unmarshalling BOMReference instances to and from XML -type bomReferenceXML struct { - Ref string `json:"-" xml:"ref,attr"` -} - -func (b BOMReference) MarshalXML(e *xml.Encoder, start xml.StartElement) error { - return e.EncodeElement(bomReferenceXML{Ref: string(b)}, start) -} - -func (b *BOMReference) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { - bXML := bomReferenceXML{} - if err := d.DecodeElement(&bXML, &start); err != nil { - return err - } - *b = BOMReference(bXML.Ref) - return nil -} - type ComponentType string const ( @@ -185,19 +163,6 @@ type Copyright struct { Text string `json:"text" xml:"-"` } -func (c Copyright) MarshalXML(e *xml.Encoder, start xml.StartElement) error { - return e.EncodeElement(c.Text, start) -} - -func (c *Copyright) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { - var text string - if err := d.DecodeElement(&text, &start); err != nil { - return err - } - (*c).Text = text - return nil -} - type Credits struct { Organizations *[]OrganizationalEntity `json:"organizations,omitempty" xml:"organizations>organization,omitempty"` Individuals *[]OrganizationalContact `json:"individuals,omitempty" xml:"individuals>individual,omitempty"` @@ -222,52 +187,6 @@ type Dependency struct { Dependencies *[]Dependency `xml:"dependency,omitempty"` } -// dependencyJSON is temporarily used for marshalling and unmarshalling Dependency instances to and from JSON -type dependencyJSON struct { - Ref string `json:"ref"` - DependsOn []string `json:"dependsOn,omitempty"` -} - -func (d Dependency) MarshalJSON() ([]byte, error) { - if d.Dependencies == nil || len(*d.Dependencies) == 0 { - return json.Marshal(&dependencyJSON{ - Ref: d.Ref, - }) - } - - dependencyRefs := make([]string, len(*d.Dependencies)) - for i, dependency := range *d.Dependencies { - dependencyRefs[i] = dependency.Ref - } - - return json.Marshal(&dependencyJSON{ - Ref: d.Ref, - DependsOn: dependencyRefs, - }) -} - -func (d *Dependency) UnmarshalJSON(bytes []byte) error { - dependency := new(dependencyJSON) - if err := json.Unmarshal(bytes, dependency); err != nil { - return err - } - d.Ref = dependency.Ref - - if len(dependency.DependsOn) == 0 { - return nil - } - - dependencies := make([]Dependency, len(dependency.DependsOn)) - for i, dep := range dependency.DependsOn { - dependencies[i] = Dependency{ - Ref: dep, - } - } - d.Dependencies = &dependencies - - return nil -} - type Diff struct { Text *AttachedText `json:"text,omitempty" xml:"text,omitempty"` URL string `json:"url,omitempty" xml:"url,omitempty"` @@ -394,70 +313,6 @@ type License struct { type Licenses []LicenseChoice -func (l Licenses) MarshalXML(e *xml.Encoder, start xml.StartElement) error { - if len(l) == 0 { - return nil - } - - if err := e.EncodeToken(start); err != nil { - return err - } - - for _, choice := range l { - if choice.License != nil && choice.Expression != "" { - return fmt.Errorf("either license or expression must be set, but not both") - } - - if choice.License != nil { - if err := e.EncodeElement(choice.License, xml.StartElement{Name: xml.Name{Local: "license"}}); err != nil { - return err - } - } else if choice.Expression != "" { - if err := e.EncodeElement(choice.Expression, xml.StartElement{Name: xml.Name{Local: "expression"}}); err != nil { - return err - } - } - } - - return e.EncodeToken(start.End()) -} - -func (l *Licenses) UnmarshalXML(d *xml.Decoder, _ xml.StartElement) error { - licenses := make([]LicenseChoice, 0) - - for { - token, err := d.Token() - if err != nil { - if errors.Is(err, io.EOF) { - break - } - return err - } - - switch tokenType := token.(type) { - case xml.StartElement: - if tokenType.Name.Local == "expression" { - var expression string - if err = d.DecodeElement(&expression, &tokenType); err != nil { - return err - } - licenses = append(licenses, LicenseChoice{Expression: expression}) - } else if tokenType.Name.Local == "license" { - var license License - if err = d.DecodeElement(&license, &tokenType); err != nil { - return err - } - licenses = append(licenses, LicenseChoice{License: &license}) - } else { - return fmt.Errorf("unknown element: %s", tokenType.Name.Local) - } - } - } - - *l = licenses - return nil -} - type LicenseChoice struct { License *License `json:"license,omitempty" xml:"-"` Expression string `json:"expression,omitempty" xml:"-"` @@ -599,60 +454,6 @@ const ( SpecVersion1_4 // 1.4 ) -func (sv SpecVersion) MarshalJSON() ([]byte, error) { - return json.Marshal(sv.String()) -} - -func (sv *SpecVersion) UnmarshalJSON(bytes []byte) error { - var v string - err := json.Unmarshal(bytes, &v) - if err != nil { - return err - } - - switch v { - case SpecVersion1_0.String(): - *sv = SpecVersion1_0 - case SpecVersion1_1.String(): - *sv = SpecVersion1_1 - case SpecVersion1_2.String(): - *sv = SpecVersion1_2 - case SpecVersion1_3.String(): - *sv = SpecVersion1_3 - case SpecVersion1_4.String(): - *sv = SpecVersion1_4 - } - - return nil -} - -func (sv SpecVersion) MarshalXML(e *xml.Encoder, start xml.StartElement) error { - return e.EncodeElement(sv.String(), start) -} - -func (sv *SpecVersion) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { - var v string - err := d.DecodeElement(&v, &start) - if err != nil { - return err - } - - switch v { - case SpecVersion1_0.String(): - *sv = SpecVersion1_0 - case SpecVersion1_1.String(): - *sv = SpecVersion1_1 - case SpecVersion1_2.String(): - *sv = SpecVersion1_2 - case SpecVersion1_3.String(): - *sv = SpecVersion1_3 - case SpecVersion1_4.String(): - *sv = SpecVersion1_4 - } - - return nil -} - type SWID struct { Text *AttachedText `json:"text,omitempty" xml:"text,omitempty"` URL string `json:"url,omitempty" xml:"url,attr,omitempty"` @@ -720,11 +521,3 @@ const ( VulnerabilityStatusAffected VulnerabilityStatus = "affected" VulnerabilityStatusNotAffected VulnerabilityStatus = "unaffected" ) - -var xmlNamespaces = map[SpecVersion]string{ - SpecVersion1_0: "http://cyclonedx.org/schema/bom/1.0", - SpecVersion1_1: "http://cyclonedx.org/schema/bom/1.1", - SpecVersion1_2: "http://cyclonedx.org/schema/bom/1.2", - SpecVersion1_3: "http://cyclonedx.org/schema/bom/1.3", - SpecVersion1_4: "http://cyclonedx.org/schema/bom/1.4", -} diff --git a/cyclonedx_json.go b/cyclonedx_json.go new file mode 100644 index 0000000..3efdbde --- /dev/null +++ b/cyclonedx_json.go @@ -0,0 +1,93 @@ +// This file is part of CycloneDX Go +// +// Licensed under the Apache License, Version 2.0 (the “License”); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an “AS IS” BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) OWASP Foundation. All Rights Reserved. + +package cyclonedx + +import "encoding/json" + +// dependencyJSON is temporarily used for marshalling and unmarshalling Dependency instances to and from JSON +type dependencyJSON struct { + Ref string `json:"ref"` + DependsOn []string `json:"dependsOn,omitempty"` +} + +func (d Dependency) MarshalJSON() ([]byte, error) { + if d.Dependencies == nil || len(*d.Dependencies) == 0 { + return json.Marshal(&dependencyJSON{ + Ref: d.Ref, + }) + } + + dependencyRefs := make([]string, len(*d.Dependencies)) + for i, dependency := range *d.Dependencies { + dependencyRefs[i] = dependency.Ref + } + + return json.Marshal(&dependencyJSON{ + Ref: d.Ref, + DependsOn: dependencyRefs, + }) +} + +func (d *Dependency) UnmarshalJSON(bytes []byte) error { + dependency := new(dependencyJSON) + if err := json.Unmarshal(bytes, dependency); err != nil { + return err + } + d.Ref = dependency.Ref + + if len(dependency.DependsOn) == 0 { + return nil + } + + dependencies := make([]Dependency, len(dependency.DependsOn)) + for i, dep := range dependency.DependsOn { + dependencies[i] = Dependency{ + Ref: dep, + } + } + d.Dependencies = &dependencies + + return nil +} + +func (sv SpecVersion) MarshalJSON() ([]byte, error) { + return json.Marshal(sv.String()) +} + +func (sv *SpecVersion) UnmarshalJSON(bytes []byte) error { + var v string + err := json.Unmarshal(bytes, &v) + if err != nil { + return err + } + + switch v { + case SpecVersion1_0.String(): + *sv = SpecVersion1_0 + case SpecVersion1_1.String(): + *sv = SpecVersion1_1 + case SpecVersion1_2.String(): + *sv = SpecVersion1_2 + case SpecVersion1_3.String(): + *sv = SpecVersion1_3 + case SpecVersion1_4.String(): + *sv = SpecVersion1_4 + } + + return nil +} diff --git a/cyclonedx_json_test.go b/cyclonedx_json_test.go new file mode 100644 index 0000000..7d0099e --- /dev/null +++ b/cyclonedx_json_test.go @@ -0,0 +1,77 @@ +// This file is part of CycloneDX Go +// +// Licensed under the Apache License, Version 2.0 (the “License”); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an “AS IS” BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) OWASP Foundation. All Rights Reserved. + +package cyclonedx + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDependency_MarshalJSON(t *testing.T) { + // Marshal empty dependency + dependency := Dependency{} + jsonBytes, err := json.Marshal(dependency) + assert.NoError(t, err) + assert.Equal(t, "{\"ref\":\"\"}", string(jsonBytes)) + + // Marshal dependency with empty dependencies + dependency = Dependency{ + Ref: "dependencyRef", + Dependencies: &[]Dependency{}, + } + jsonBytes, err = json.Marshal(dependency) + assert.NoError(t, err) + assert.Equal(t, "{\"ref\":\"dependencyRef\"}", string(jsonBytes)) + + // Marshal dependency with dependencies + dependency = Dependency{ + Ref: "dependencyRef", + Dependencies: &[]Dependency{ + {Ref: "transitiveDependencyRef"}, + }, + } + jsonBytes, err = json.Marshal(dependency) + assert.NoError(t, err) + assert.Equal(t, "{\"ref\":\"dependencyRef\",\"dependsOn\":[\"transitiveDependencyRef\"]}", string(jsonBytes)) +} + +func TestDependency_UnmarshalJSON(t *testing.T) { + // Unmarshal empty dependency + dependency := new(Dependency) + err := json.Unmarshal([]byte("{}"), dependency) + assert.NoError(t, err) + assert.Equal(t, "", dependency.Ref) + assert.Nil(t, dependency.Dependencies) + + // Unmarshal dependency with empty dependencies + dependency = new(Dependency) + err = json.Unmarshal([]byte("{\"ref\":\"dependencyRef\",\"dependsOn\":[]}"), dependency) + assert.NoError(t, err) + assert.Equal(t, "dependencyRef", dependency.Ref) + assert.Nil(t, dependency.Dependencies) + + // Unmarshal dependency with dependencies + dependency = new(Dependency) + err = json.Unmarshal([]byte("{\"ref\":\"dependencyRef\",\"dependsOn\":[\"transitiveDependencyRef\"]}"), dependency) + assert.NoError(t, err) + assert.Equal(t, "dependencyRef", dependency.Ref) + assert.Equal(t, 1, len(*dependency.Dependencies)) + assert.Equal(t, "transitiveDependencyRef", (*dependency.Dependencies)[0].Ref) +} diff --git a/cyclonedx_test.go b/cyclonedx_test.go index 7605d47..bfcb8fd 100644 --- a/cyclonedx_test.go +++ b/cyclonedx_test.go @@ -18,8 +18,6 @@ package cyclonedx import ( - "encoding/json" - "encoding/xml" "fmt" "os/exec" "strings" @@ -27,7 +25,6 @@ import ( "github.com/bradleyjkemp/cupaloy/v2" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) var snapShooter = cupaloy.NewDefaultConfig(). @@ -38,193 +35,6 @@ func TestBool(t *testing.T) { assert.Equal(t, false, *Bool(false)) } -func TestBOMReference_MarshalXML(t *testing.T) { - // Marshal empty bomRef - bomRef := BOMReference("") - xmlBytes, err := xml.Marshal(bomRef) - assert.NoError(t, err) - assert.Equal(t, "", string(xmlBytes)) - - // Marshal bomRef - bomRef = "bomRef" - xmlBytes, err = xml.Marshal(bomRef) - assert.NoError(t, err) - assert.Equal(t, "", string(xmlBytes)) -} - -func TestBOMReference_UnmarshalXML(t *testing.T) { - // Unmarshal empty bomRef - bomRef := new(BOMReference) - err := xml.Unmarshal([]byte(""), bomRef) - require.NoError(t, err) - require.Equal(t, "", string(*bomRef)) - - // Unmarshal bomRef - err = xml.Unmarshal([]byte(""), bomRef) - require.NoError(t, err) - require.Equal(t, "bomRef", string(*bomRef)) -} - -func TestCopyright_MarshalXML(t *testing.T) { - // Marshal empty copyright - copyright := Copyright{} - xmlBytes, err := xml.Marshal(copyright) - require.NoError(t, err) - require.Equal(t, "", string(xmlBytes)) - - // Marshal copyright - copyright.Text = "copyright" - xmlBytes, err = xml.Marshal(copyright) - require.NoError(t, err) - require.Equal(t, "copyright", string(xmlBytes)) -} - -func TestCopyright_UnmarshalXML(t *testing.T) { - // Unmarshal empty copyright - copyright := new(Copyright) - err := xml.Unmarshal([]byte(""), copyright) - require.NoError(t, err) - require.Equal(t, "", copyright.Text) - - // Unmarshal copyright - err = xml.Unmarshal([]byte("copyright"), copyright) - require.NoError(t, err) - require.Equal(t, "copyright", copyright.Text) -} - -func TestDependency_MarshalJSON(t *testing.T) { - // Marshal empty dependency - dependency := Dependency{} - jsonBytes, err := json.Marshal(dependency) - assert.NoError(t, err) - assert.Equal(t, "{\"ref\":\"\"}", string(jsonBytes)) - - // Marshal dependency with empty dependencies - dependency = Dependency{ - Ref: "dependencyRef", - Dependencies: &[]Dependency{}, - } - jsonBytes, err = json.Marshal(dependency) - assert.NoError(t, err) - assert.Equal(t, "{\"ref\":\"dependencyRef\"}", string(jsonBytes)) - - // Marshal dependency with dependencies - dependency = Dependency{ - Ref: "dependencyRef", - Dependencies: &[]Dependency{ - {Ref: "transitiveDependencyRef"}, - }, - } - jsonBytes, err = json.Marshal(dependency) - assert.NoError(t, err) - assert.Equal(t, "{\"ref\":\"dependencyRef\",\"dependsOn\":[\"transitiveDependencyRef\"]}", string(jsonBytes)) -} - -func TestDependency_UnmarshalJSON(t *testing.T) { - // Unmarshal empty dependency - dependency := new(Dependency) - err := json.Unmarshal([]byte("{}"), dependency) - assert.NoError(t, err) - assert.Equal(t, "", dependency.Ref) - assert.Nil(t, dependency.Dependencies) - - // Unmarshal dependency with empty dependencies - dependency = new(Dependency) - err = json.Unmarshal([]byte("{\"ref\":\"dependencyRef\",\"dependsOn\":[]}"), dependency) - assert.NoError(t, err) - assert.Equal(t, "dependencyRef", dependency.Ref) - assert.Nil(t, dependency.Dependencies) - - // Unmarshal dependency with dependencies - dependency = new(Dependency) - err = json.Unmarshal([]byte("{\"ref\":\"dependencyRef\",\"dependsOn\":[\"transitiveDependencyRef\"]}"), dependency) - assert.NoError(t, err) - assert.Equal(t, "dependencyRef", dependency.Ref) - assert.Equal(t, 1, len(*dependency.Dependencies)) - assert.Equal(t, "transitiveDependencyRef", (*dependency.Dependencies)[0].Ref) -} - -func TestLicenses_MarshalXML(t *testing.T) { - // Marshal license and expressions - licenses := Licenses{ - LicenseChoice{ - Expression: "expressionValue1", - }, - LicenseChoice{ - License: &License{ - ID: "licenseID", - URL: "licenseURL", - }, - }, - LicenseChoice{ - Expression: "expressionValue2", - }, - } - xmlBytes, err := xml.MarshalIndent(licenses, "", " ") - assert.NoError(t, err) - assert.Equal(t, ` - expressionValue1 - - licenseID - licenseURL - - expressionValue2 -`, string(xmlBytes)) - - // Should return error when both license and expression are set on an element - licenses = Licenses{ - LicenseChoice{ - License: &License{ - ID: "licenseID", - }, - Expression: "expressionValue", - }, - } - _, err = xml.Marshal(licenses) - assert.Error(t, err) - - // Should encode nothing when empty - licenses = Licenses{} - xmlBytes, err = xml.Marshal(licenses) - assert.NoError(t, err) - assert.Nil(t, xmlBytes) -} - -func TestLicenses_UnmarshalXML(t *testing.T) { - // Unmarshal license and expressions - licenses := new(Licenses) - err := xml.Unmarshal([]byte(` - - expressionValue1 - - licenseID - licenseURL - - expressionValue2 -`), licenses) - assert.NoError(t, err) - assert.Len(t, *licenses, 3) - assert.Nil(t, (*licenses)[0].License) - assert.Equal(t, "expressionValue1", (*licenses)[0].Expression) - assert.NotNil(t, (*licenses)[1].License) - assert.Equal(t, "licenseID", (*licenses)[1].License.ID) - assert.Equal(t, "licenseURL", (*licenses)[1].License.URL) - assert.Empty(t, (*licenses)[1].Expression) - assert.Nil(t, (*licenses)[2].License) - assert.Equal(t, "expressionValue2", (*licenses)[2].Expression) - - // Unmarshal empty licenses - licenses = new(Licenses) - err = xml.Unmarshal([]byte(""), licenses) - assert.NoError(t, err) - assert.Empty(t, *licenses) - - // Should return error when an element is neither license nor expression - licenses = new(Licenses) - err = xml.Unmarshal([]byte("expressionValue"), licenses) - assert.Error(t, err) -} - func TestVulnerability_Properties(t *testing.T) { // GIVEN properties := []Property{} diff --git a/cyclonedx_xml.go b/cyclonedx_xml.go new file mode 100644 index 0000000..83db3c5 --- /dev/null +++ b/cyclonedx_xml.go @@ -0,0 +1,155 @@ +// This file is part of CycloneDX Go +// +// Licensed under the Apache License, Version 2.0 (the “License”); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an “AS IS” BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) OWASP Foundation. All Rights Reserved. + +package cyclonedx + +import ( + "encoding/xml" + "errors" + "fmt" + "io" +) + +// bomReferenceXML is temporarily used for marshalling and unmarshalling BOMReference instances to and from XML +type bomReferenceXML struct { + Ref string `json:"-" xml:"ref,attr"` +} + +func (b BOMReference) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + return e.EncodeElement(bomReferenceXML{Ref: string(b)}, start) +} + +func (b *BOMReference) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + bXML := bomReferenceXML{} + if err := d.DecodeElement(&bXML, &start); err != nil { + return err + } + *b = BOMReference(bXML.Ref) + return nil +} + +func (c Copyright) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + return e.EncodeElement(c.Text, start) +} + +func (c *Copyright) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var text string + if err := d.DecodeElement(&text, &start); err != nil { + return err + } + (*c).Text = text + return nil +} + +func (l Licenses) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + if len(l) == 0 { + return nil + } + + if err := e.EncodeToken(start); err != nil { + return err + } + + for _, choice := range l { + if choice.License != nil && choice.Expression != "" { + return fmt.Errorf("either license or expression must be set, but not both") + } + + if choice.License != nil { + if err := e.EncodeElement(choice.License, xml.StartElement{Name: xml.Name{Local: "license"}}); err != nil { + return err + } + } else if choice.Expression != "" { + if err := e.EncodeElement(choice.Expression, xml.StartElement{Name: xml.Name{Local: "expression"}}); err != nil { + return err + } + } + } + + return e.EncodeToken(start.End()) +} + +func (l *Licenses) UnmarshalXML(d *xml.Decoder, _ xml.StartElement) error { + licenses := make([]LicenseChoice, 0) + + for { + token, err := d.Token() + if err != nil { + if errors.Is(err, io.EOF) { + break + } + return err + } + + switch tokenType := token.(type) { + case xml.StartElement: + if tokenType.Name.Local == "expression" { + var expression string + if err = d.DecodeElement(&expression, &tokenType); err != nil { + return err + } + licenses = append(licenses, LicenseChoice{Expression: expression}) + } else if tokenType.Name.Local == "license" { + var license License + if err = d.DecodeElement(&license, &tokenType); err != nil { + return err + } + licenses = append(licenses, LicenseChoice{License: &license}) + } else { + return fmt.Errorf("unknown element: %s", tokenType.Name.Local) + } + } + } + + *l = licenses + return nil +} + +func (sv SpecVersion) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + return e.EncodeElement(sv.String(), start) +} + +func (sv *SpecVersion) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var v string + err := d.DecodeElement(&v, &start) + if err != nil { + return err + } + + switch v { + case SpecVersion1_0.String(): + *sv = SpecVersion1_0 + case SpecVersion1_1.String(): + *sv = SpecVersion1_1 + case SpecVersion1_2.String(): + *sv = SpecVersion1_2 + case SpecVersion1_3.String(): + *sv = SpecVersion1_3 + case SpecVersion1_4.String(): + *sv = SpecVersion1_4 + } + + return nil +} + +var xmlNamespaces = map[SpecVersion]string{ + SpecVersion1_0: "http://cyclonedx.org/schema/bom/1.0", + SpecVersion1_1: "http://cyclonedx.org/schema/bom/1.1", + SpecVersion1_2: "http://cyclonedx.org/schema/bom/1.2", + SpecVersion1_3: "http://cyclonedx.org/schema/bom/1.3", + SpecVersion1_4: "http://cyclonedx.org/schema/bom/1.4", +} diff --git a/cyclonedx_xml_test.go b/cyclonedx_xml_test.go new file mode 100644 index 0000000..a7f332e --- /dev/null +++ b/cyclonedx_xml_test.go @@ -0,0 +1,161 @@ +// This file is part of CycloneDX Go +// +// Licensed under the Apache License, Version 2.0 (the “License”); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an “AS IS” BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) OWASP Foundation. All Rights Reserved. + +package cyclonedx + +import ( + "encoding/xml" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestBOMReference_MarshalXML(t *testing.T) { + // Marshal empty bomRef + bomRef := BOMReference("") + xmlBytes, err := xml.Marshal(bomRef) + assert.NoError(t, err) + assert.Equal(t, "", string(xmlBytes)) + + // Marshal bomRef + bomRef = "bomRef" + xmlBytes, err = xml.Marshal(bomRef) + assert.NoError(t, err) + assert.Equal(t, "", string(xmlBytes)) +} + +func TestBOMReference_UnmarshalXML(t *testing.T) { + // Unmarshal empty bomRef + bomRef := new(BOMReference) + err := xml.Unmarshal([]byte(""), bomRef) + require.NoError(t, err) + require.Equal(t, "", string(*bomRef)) + + // Unmarshal bomRef + err = xml.Unmarshal([]byte(""), bomRef) + require.NoError(t, err) + require.Equal(t, "bomRef", string(*bomRef)) +} + +func TestCopyright_MarshalXML(t *testing.T) { + // Marshal empty copyright + copyright := Copyright{} + xmlBytes, err := xml.Marshal(copyright) + require.NoError(t, err) + require.Equal(t, "", string(xmlBytes)) + + // Marshal copyright + copyright.Text = "copyright" + xmlBytes, err = xml.Marshal(copyright) + require.NoError(t, err) + require.Equal(t, "copyright", string(xmlBytes)) +} + +func TestCopyright_UnmarshalXML(t *testing.T) { + // Unmarshal empty copyright + copyright := new(Copyright) + err := xml.Unmarshal([]byte(""), copyright) + require.NoError(t, err) + require.Equal(t, "", copyright.Text) + + // Unmarshal copyright + err = xml.Unmarshal([]byte("copyright"), copyright) + require.NoError(t, err) + require.Equal(t, "copyright", copyright.Text) +} + +func TestLicenses_MarshalXML(t *testing.T) { + // Marshal license and expressions + licenses := Licenses{ + LicenseChoice{ + Expression: "expressionValue1", + }, + LicenseChoice{ + License: &License{ + ID: "licenseID", + URL: "licenseURL", + }, + }, + LicenseChoice{ + Expression: "expressionValue2", + }, + } + xmlBytes, err := xml.MarshalIndent(licenses, "", " ") + assert.NoError(t, err) + assert.Equal(t, ` + expressionValue1 + + licenseID + licenseURL + + expressionValue2 +`, string(xmlBytes)) + + // Should return error when both license and expression are set on an element + licenses = Licenses{ + LicenseChoice{ + License: &License{ + ID: "licenseID", + }, + Expression: "expressionValue", + }, + } + _, err = xml.Marshal(licenses) + assert.Error(t, err) + + // Should encode nothing when empty + licenses = Licenses{} + xmlBytes, err = xml.Marshal(licenses) + assert.NoError(t, err) + assert.Nil(t, xmlBytes) +} + +func TestLicenses_UnmarshalXML(t *testing.T) { + // Unmarshal license and expressions + licenses := new(Licenses) + err := xml.Unmarshal([]byte(` + + expressionValue1 + + licenseID + licenseURL + + expressionValue2 +`), licenses) + assert.NoError(t, err) + assert.Len(t, *licenses, 3) + assert.Nil(t, (*licenses)[0].License) + assert.Equal(t, "expressionValue1", (*licenses)[0].Expression) + assert.NotNil(t, (*licenses)[1].License) + assert.Equal(t, "licenseID", (*licenses)[1].License.ID) + assert.Equal(t, "licenseURL", (*licenses)[1].License.URL) + assert.Empty(t, (*licenses)[1].Expression) + assert.Nil(t, (*licenses)[2].License) + assert.Equal(t, "expressionValue2", (*licenses)[2].Expression) + + // Unmarshal empty licenses + licenses = new(Licenses) + err = xml.Unmarshal([]byte(""), licenses) + assert.NoError(t, err) + assert.Empty(t, *licenses) + + // Should return error when an element is neither license nor expression + licenses = new(Licenses) + err = xml.Unmarshal([]byte("expressionValue"), licenses) + assert.Error(t, err) +}