Skip to content

Commit 9ee4d97

Browse files
committed
Merge branch 'feat/my/component_evidence_itentiyt_single_deserialize' into fix/component-evidence-identity-deserialization
- remove JSON-only implemntations and tests - added more tests - streamlined some imeplementations Signed-off-by: Jan Kowalleck <jan.kowalleck@gmail.com>
2 parents 7d3e6f6 + 054334b commit 9ee4d97

8 files changed

Lines changed: 185 additions & 168 deletions

cyclonedx/model/component_evidence.py

Lines changed: 10 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -185,24 +185,6 @@ def xml_denormalize(cls, o: 'XmlElement', *,
185185
return [BomRef(value=t.get('ref')) for t in o]
186186

187187

188-
class _IdentitySetSerializationHelper(serializable.helpers.BaseHelper):
189-
""" THIS CLASS IS NON-PUBLIC API """
190-
191-
@classmethod
192-
def json_normalize(cls, o: Iterable['Identity'], *,
193-
view: Optional[type[serializable.ViewType]],
194-
**__: Any) -> list[dict[str, Any]]:
195-
# Serialize identity as a list of dicts, preserving the view context
196-
return [json_loads(item.as_json(view)) for item in o] # type: ignore[attr-defined]
197-
198-
@classmethod
199-
def json_deserialize(cls, o: Union[dict[str, Any], list[dict[str, Any]]]) -> list['Identity']:
200-
# Handle identity field which can be a dict (CycloneDX 1.5) or list of dicts (CycloneDX 1.6)
201-
if isinstance(o, dict):
202-
return [Identity.from_json(o)] # type: ignore[attr-defined]
203-
return [Identity.from_json(item) for item in o] # type: ignore[attr-defined]
204-
205-
206188
@serializable.serializable_class(ignore_unknown_during_deserialization=True)
207189
class Identity:
208190
"""
@@ -672,7 +654,6 @@ def __init__(
672654
@property
673655
@serializable.view(SchemaVersion1Dot5)
674656
@serializable.view(SchemaVersion1Dot6)
675-
@serializable.type_mapping(_IdentitySetSerializationHelper)
676657
@serializable.xml_sequence(1)
677658
@serializable.xml_array(serializable.XmlArraySerializationType.FLAT, 'identity')
678659
def identity(self) -> 'SortedSet[Identity]':
@@ -771,7 +752,10 @@ def __repr__(self) -> str:
771752

772753

773754
class _ComponentEvidenceSerializationHelper(serializable.helpers.BaseHelper):
774-
"""THIS CLASS IS NON-PUBLIC API"""
755+
"""THIS CLASS IS NON-PUBLIC API
756+
757+
This helper takes care of :attr:`ComponentEvidence.identity`.
758+
"""
775759

776760
@classmethod
777761
def json_normalize(cls, o: ComponentEvidence, *,
@@ -780,17 +764,16 @@ def json_normalize(cls, o: ComponentEvidence, *,
780764
data: dict[str, Any] = json_loads(o.as_json(view)) # type:ignore[attr-defined]
781765
if view is SchemaVersion1Dot5:
782766
identities = data.get('identity', [])
783-
if il := len(identities) > 1:
784-
warn(f'CycloneDX 1.5 does not support multiple identity items; dropping {il - 1} items.')
767+
if identities:
768+
if (il := len(identities)) > 1:
769+
warn(f'CycloneDX 1.5 does not support multiple identity items; dropping {il - 1} items.')
785770
data['identity'] = identities[0]
786771
return data
787772

788773
@classmethod
789774
def json_denormalize(cls, o: dict[str, Any], **__: Any) -> Any:
790-
# Handle identity field which can be a dict (CycloneDX 1.5) or list of dicts (CycloneDX 1.6)
791-
# Before passing to ComponentEvidence.from_json, ensure it's always a list
792-
if 'identity' in o and isinstance(o['identity'], dict):
793-
o = {**o, 'identity': [o['identity']]}
775+
if isinstance(identity := o.get('identity'), dict):
776+
o = {**o, 'identity': [identity]}
794777
return ComponentEvidence.from_json(o) # type:ignore[attr-defined]
795778

796779
@classmethod
@@ -802,7 +785,7 @@ def xml_normalize(cls, o: ComponentEvidence, *,
802785
normalized: 'XmlElement' = o.as_xml(view, False, element_name, xmlns) # type:ignore[attr-defined]
803786
if view is SchemaVersion1Dot5:
804787
identities = normalized.findall(f'./{{{xmlns}}}identity' if xmlns else './identity')
805-
if il := len(identities) > 1:
788+
if (il := len(identities)) > 1:
806789
warn(f'CycloneDX 1.5 does not support multiple identity items; dropping {il - 1} items.')
807790
for i in identities[1:]:
808791
normalized.remove(i)

tests/_data/own/json/1.5/component_evidence_identity.json

Lines changed: 39 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/_data/own/json/1.6/component_evidence_identity.json

Lines changed: 45 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/_data/own/xml/1.5/component_evidence_identity.xml

Lines changed: 33 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/_data/own/xml/1.6/component_evidence_identity.xml

Lines changed: 37 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/test_deserialize_json.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,12 @@ def test_regression_issue690(self) -> None:
127127
json = json_loads(f.read())
128128
bom: Bom = Bom.from_json(json) # <<< is expected to not crash
129129
self.assertIsNotNone(bom)
130+
131+
def test_component_evidence_identity(self) -> None:
132+
json_file = join(OWN_DATA_DIRECTORY, 'json',
133+
SchemaVersion.V1_6.to_version(),
134+
'component_evidence_identity.json')
135+
with open(json_file) as f:
136+
json = json_loads(f.read())
137+
bom: Bom = Bom.from_json(json) # <<< is expected to not crash
138+
self.assertIsNotNone(bom)

tests/test_deserialize_xml.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
# Copyright (c) OWASP Foundation. All Rights Reserved.
1717

1818
from collections.abc import Callable
19+
from os.path import join
1920
from typing import Any
2021
from unittest import TestCase
2122
from unittest.mock import patch
@@ -24,7 +25,7 @@
2425

2526
from cyclonedx.model.bom import Bom
2627
from cyclonedx.schema import OutputFormat, SchemaVersion
27-
from tests import DeepCompareMixin, SnapshotMixin, mksname
28+
from tests import OWN_DATA_DIRECTORY, DeepCompareMixin, SnapshotMixin, mksname
2829
from tests._data.models import (
2930
all_get_bom_funct_valid_immut,
3031
all_get_bom_funct_valid_reversible_migrate,
@@ -46,3 +47,11 @@ def test_prepared(self, get_bom: Callable[[], Bom], *_: Any, **__: Any) -> None:
4647
bom = Bom.from_xml(s)
4748
self.assertBomDeepEqual(expected, bom,
4849
fuzzy_deps=get_bom in all_get_bom_funct_with_incomplete_deps)
50+
51+
def test_component_evidence_identity(self) -> None:
52+
xml_file = join(OWN_DATA_DIRECTORY, 'xml',
53+
SchemaVersion.V1_6.to_version(),
54+
'component_evidence_identity.xml')
55+
with open(xml_file) as f:
56+
bom: Bom = Bom.from_xml(f) # <<< is expected to not crash
57+
self.assertIsNotNone(bom)

0 commit comments

Comments
 (0)