Skip to content

Commit

Permalink
JSONv unit tests passing except those blocked by CycloneDX/specificat…
Browse files Browse the repository at this point in the history
…ion#146

Signed-off-by: Paul Horton <[email protected]>
  • Loading branch information
madpah committed Jan 23, 2023
1 parent 40357f4 commit 9f88ce8
Show file tree
Hide file tree
Showing 28 changed files with 219 additions and 135 deletions.
9 changes: 8 additions & 1 deletion cyclonedx/model/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@
NoPropertiesProvidedException,
UnknownHashTypeException,
)
from ..schema.schema import (
SchemaVersion1Dot3,
SchemaVersion1Dot4
)

"""
Uniform set of models to represent objects within a CycloneDX software bill-of-materials.
Expand Down Expand Up @@ -542,8 +546,10 @@ def type_(self, type_: ExternalReferenceType) -> None:
self._type_ = type_

@property # type: ignore[misc]
@serializable.view(SchemaVersion1Dot3)
@serializable.view(SchemaVersion1Dot4)
@serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'hash')
def hashes(self) -> "Optional[SortedSet[HashType]]":
def hashes(self) -> "SortedSet[HashType]":
"""
The hashes of the external reference (if applicable).
Expand Down Expand Up @@ -1227,6 +1233,7 @@ def hashes(self, hashes: Iterable[HashType]) -> None:
self._hashes = SortedSet(hashes)

@property # type: ignore[misc]
@serializable.view(SchemaVersion1Dot4)
@serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'reference')
@serializable.xml_sequence(5)
def external_references(self) -> "SortedSet[ExternalReference]":
Expand Down
12 changes: 12 additions & 0 deletions cyclonedx/model/bom.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@

from ..exception.model import UnknownComponentDependencyException
from ..parser import BaseParser
from ..schema.schema import (
SchemaVersion1Dot0,
SchemaVersion1Dot1,
SchemaVersion1Dot2,
SchemaVersion1Dot3,
SchemaVersion1Dot4
)
from . import ExternalReference, LicenseChoice, OrganizationalContact, OrganizationalEntity, Property, ThisTool, Tool
from .bom_ref import BomRef
from .component import Component
Expand Down Expand Up @@ -173,6 +180,8 @@ def supplier(self, supplier: Optional[OrganizationalEntity]) -> None:
self._supplier = supplier

@property # type: ignore[misc]
@serializable.view(SchemaVersion1Dot3)
@serializable.view(SchemaVersion1Dot4)
@serializable.xml_array(serializable.XmlArraySerializationType.FLAT, 'licenses')
@serializable.xml_sequence(7)
def licenses(self) -> "SortedSet[LicenseChoice]":
Expand All @@ -189,6 +198,8 @@ def licenses(self, licenses: Iterable[LicenseChoice]) -> None:
self._licenses = SortedSet(licenses)

@property # type: ignore[misc]
@serializable.view(SchemaVersion1Dot3)
@serializable.view(SchemaVersion1Dot4)
@serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'property')
@serializable.xml_sequence(8)
def properties(self) -> "SortedSet[Property]":
Expand Down Expand Up @@ -436,6 +447,7 @@ def has_vulnerabilities(self) -> bool:
return bool(self.vulnerabilities)

@property # type: ignore[misc]
@serializable.view(SchemaVersion1Dot4)
@serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'vulnerability')
@serializable.xml_sequence(8)
def vulnerabilities(self) -> "SortedSet[Vulnerability]":
Expand Down
22 changes: 19 additions & 3 deletions cyclonedx/model/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@
from .dependency import Dependable
from .issue import IssueType
from .release_note import ReleaseNotes
from ..schema.schema import (
SchemaVersion1Dot0,
SchemaVersion1Dot1,
SchemaVersion1Dot2,
SchemaVersion1Dot3,
SchemaVersion1Dot4
)


@serializable.serializable_class
Expand Down Expand Up @@ -156,7 +163,7 @@ def __eq__(self, other: object) -> bool:
def __lt__(self, other: Any) -> bool:
if isinstance(other, Commit):
return ComparableTuple((self.uid, self.url, self.author, self.committer, self.message)) < \
ComparableTuple((other.uid, other.url, other.author, other.committer, other.message))
ComparableTuple((other.uid, other.url, other.author, other.committer, other.message))
return NotImplemented

def __hash__(self) -> int:
Expand Down Expand Up @@ -404,7 +411,7 @@ def __eq__(self, other: object) -> bool:
def __lt__(self, other: Any) -> bool:
if isinstance(other, Patch):
return ComparableTuple((self.type_, self.diff, ComparableTuple(self.resolves))) < \
ComparableTuple((other.type_, other.diff, ComparableTuple(other.resolves)))
ComparableTuple((other.type_, other.diff, ComparableTuple(other.resolves)))
return NotImplemented

def __hash__(self) -> int:
Expand Down Expand Up @@ -932,6 +939,10 @@ def name(self, name: str) -> None:
self._name = name

@property # type: ignore[misc]
@serializable.include_none(SchemaVersion1Dot0)
@serializable.include_none(SchemaVersion1Dot1)
@serializable.include_none(SchemaVersion1Dot2)
@serializable.include_none(SchemaVersion1Dot3)
@serializable.xml_sequence(6)
def version(self) -> Optional[str]:
"""
Expand Down Expand Up @@ -1113,6 +1124,8 @@ def external_references(self, external_references: Iterable[ExternalReference])
self._external_references = SortedSet(external_references)

@property # type: ignore[misc]
@serializable.view(SchemaVersion1Dot3)
@serializable.view(SchemaVersion1Dot4)
@serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'property')
@serializable.xml_sequence(18)
def properties(self) -> "SortedSet[Property]":
Expand Down Expand Up @@ -1148,6 +1161,8 @@ def components(self, components: Iterable['Component']) -> None:
self._components = SortedSet(components)

@property # type: ignore[misc]
@serializable.view(SchemaVersion1Dot3)
@serializable.view(SchemaVersion1Dot4)
@serializable.xml_sequence(20)
def evidence(self) -> Optional[ComponentEvidence]:
"""
Expand All @@ -1163,6 +1178,7 @@ def evidence(self, evidence: Optional[ComponentEvidence]) -> None:
self._evidence = evidence

@property # type: ignore[misc]
@serializable.view(SchemaVersion1Dot4)
@serializable.xml_sequence(21)
def release_notes(self) -> Optional[ReleaseNotes]:
"""
Expand Down Expand Up @@ -1201,7 +1217,7 @@ def __eq__(self, other: object) -> bool:
def __lt__(self, other: Any) -> bool:
if isinstance(other, Component):
return ComparableTuple((self.type_, self.group, self.name, self.version)) < \
ComparableTuple((other.type_, other.group, other.name, other.version))
ComparableTuple((other.type_, other.group, other.name, other.version))
return NotImplemented

def __hash__(self) -> int:
Expand Down
10 changes: 10 additions & 0 deletions cyclonedx/model/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@
from .bom_ref import BomRef
from .dependency import Dependable
from .release_note import ReleaseNotes
from ..schema.schema import (
SchemaVersion1Dot0,
SchemaVersion1Dot1,
SchemaVersion1Dot2,
SchemaVersion1Dot3,
SchemaVersion1Dot4
)

"""
This set of classes represents the data that is possible about known Services.
Expand Down Expand Up @@ -294,6 +301,7 @@ def services(self, services: Iterable['Service']) -> None:
self._services = SortedSet(services)

@property # type: ignore[misc]
@serializable.view(SchemaVersion1Dot4)
@serializable.xml_sequence(14)
def release_notes(self) -> Optional[ReleaseNotes]:
"""
Expand All @@ -309,6 +317,8 @@ def release_notes(self, release_notes: Optional[ReleaseNotes]) -> None:
self._release_notes = release_notes

@property # type: ignore[misc]
@serializable.view(SchemaVersion1Dot3)
@serializable.view(SchemaVersion1Dot4)
@serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'property')
@serializable.xml_sequence(12)
def properties(self) -> "SortedSet[Property]":
Expand Down
26 changes: 2 additions & 24 deletions cyclonedx/output/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,33 +22,11 @@
import importlib
import os
from abc import ABC, abstractmethod
from enum import Enum

from typing import cast

from ..model.bom import Bom


class OutputFormat(str, Enum):
JSON: str = 'Json'
XML: str = 'Xml'


class SchemaVersion(str, Enum):
V1_0: str = 'V1Dot0'
V1_1: str = 'V1Dot1'
V1_2: str = 'V1Dot2'
V1_3: str = 'V1Dot3'
V1_4: str = 'V1Dot4'

def to_version(self) -> str:
"""
Return as a version string - e.g. `1.4`
Returns:
`str` version
"""
return f'{self.value[1]}.{self.value[5]}'

from ..schema import OutputFormat, SchemaVersion

LATEST_SUPPORTED_SCHEMA_VERSION = SchemaVersion.V1_4

Expand Down
107 changes: 53 additions & 54 deletions cyclonedx/output/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
from ..model.bom import Bom
from ..model.component import Component
from . import BaseOutput, SchemaVersion
from .schema import (
from ..schema.schema import (
SCHEMA_VERSIONS,
BaseSchemaVersion,
SchemaVersion1Dot0,
SchemaVersion1Dot1,
Expand Down Expand Up @@ -54,64 +55,62 @@ def schema_version(self) -> SchemaVersion:

def generate(self, force_regeneration: bool = False) -> None:
# New Way
if self.schema_version == SchemaVersion.V1_4:
if self.generated and force_regeneration:
self.get_bom().validate()
bom_json = json.loads(self.get_bom().as_json())
bom_json.update({
'$schema': self._get_schema_uri(),
'bomFormat': 'CycloneDX',
'specVersion': '1.4'
})
self._json_output = json.dumps(bom_json)
self.generated = True
return
elif self.generated:
return
else:
self.get_bom().validate()
bom_json = json.loads(self.get_bom().as_json())
bom_json.update({
'$schema': self._get_schema_uri(),
'bomFormat': 'CycloneDX',
'specVersion': '1.4'
})
self._json_output = json.dumps(bom_json)
self.generated = True
return

# Old Way
if self.generated and not force_regeneration:
return

bom = self.get_bom()
bom.validate()

schema_uri: Optional[str] = self._get_schema_uri()
if not schema_uri:
raise FormatNotSupportedException(
f'JSON is not supported by CycloneDX in schema version {self.schema_version.to_version()}')

extras = {}
if self.bom_supports_dependencies():
dep_components: Iterable[Component] = bom.components
if bom.metadata.component:
dep_components = [bom.metadata.component, *dep_components]
dependencies = []
for component in dep_components:
dependencies.append({
'ref': str(component.bom_ref),
'dependsOn': [*map(str, component.dependencies)]
})
if dependencies:
extras["dependencies"] = dependencies
del dep_components

bom_json = json.loads(json.dumps(bom, cls=CycloneDxJSONEncoder))
bom_json = json.loads(self._specialise_output_for_schema_version(bom_json=bom_json))
self._json_output = json.dumps({**bom_json, **self._create_bom_element(), **extras})

self.generated = True
# if self.schema_version == SchemaVersion.V1_4:
_json_core = {
'$schema': schema_uri,
'bomFormat': 'CycloneDX',
'specVersion': self.schema_version.to_version()
}
_view = SCHEMA_VERSIONS.get(self.get_schema_version())
if self.generated and force_regeneration:
self.get_bom().validate()
bom_json = json.loads(self.get_bom().as_json(view_=_view))
bom_json.update(_json_core)
self._json_output = json.dumps(bom_json)
self.generated = True
return
elif self.generated:
return
else:
self.get_bom().validate()
bom_json = json.loads(self.get_bom().as_json(view_=_view))
bom_json.update(_json_core)
self._json_output = json.dumps(bom_json)
self.generated = True
return

# Old Way
# if self.generated and not force_regeneration:
# return
#
# bom = self.get_bom()
# bom.validate()
#
# extras = {}
# if self.bom_supports_dependencies():
# dep_components: Iterable[Component] = bom.components
# if bom.metadata.component:
# dep_components = [bom.metadata.component, *dep_components]
# dependencies = []
# for component in dep_components:
# dependencies.append({
# 'ref': str(component.bom_ref),
# 'dependsOn': [*map(str, component.dependencies)]
# })
# if dependencies:
# extras["dependencies"] = dependencies
# del dep_components
#
# bom_json = json.loads(json.dumps(bom, cls=CycloneDxJSONEncoder))
# bom_json = json.loads(self._specialise_output_for_schema_version(bom_json=bom_json))
# self._json_output = json.dumps({**bom_json, **self._create_bom_element(), **extras})
#
# self.generated = True

def _specialise_output_for_schema_version(self, bom_json: Dict[Any, Any]) -> str:
if not self.bom_supports_metadata():
Expand Down
39 changes: 39 additions & 0 deletions cyclonedx/schema/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# encoding: utf-8

# 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

from enum import Enum


class OutputFormat(str, Enum):
JSON: str = 'Json'
XML: str = 'Xml'


class SchemaVersion(str, Enum):
V1_0: str = 'V1Dot0'
V1_1: str = 'V1Dot1'
V1_2: str = 'V1Dot2'
V1_3: str = 'V1Dot3'
V1_4: str = 'V1Dot4'

def to_version(self) -> str:
"""
Return as a version string - e.g. `1.4`

Returns:
`str` version
"""
return f'{self.value[1]}.{self.value[5]}'
Loading

0 comments on commit 9f88ce8

Please sign in to comment.