Skip to content

Commit 5de7307

Browse files
committed
fix component evidence serialization
Signed-off-by: Jan Kowalleck <[email protected]>
1 parent 47a1ef4 commit 5de7307

File tree

2 files changed

+45
-27
lines changed

2 files changed

+45
-27
lines changed

cyclonedx/model/component.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
_HashTypeRepositorySerializationHelper,
5858
)
5959
from .bom_ref import BomRef
60-
from .component_evidence import ComponentEvidence
60+
from .component_evidence import ComponentEvidence, _ComponentEvidenceSerializationHelper
6161
from .contact import OrganizationalContact, OrganizationalEntity
6262
from .crypto import CryptoProperties
6363
from .dependency import Dependable
@@ -1542,6 +1542,7 @@ def components(self, components: Iterable['Component']) -> None:
15421542
@serializable.view(SchemaVersion1Dot5)
15431543
@serializable.view(SchemaVersion1Dot6)
15441544
@serializable.xml_sequence(24)
1545+
@serializable.type_mapping(_ComponentEvidenceSerializationHelper)
15451546
def evidence(self) -> Optional[ComponentEvidence]:
15461547
"""
15471548
Provides the ability to document evidence collected through various forms of extraction or analysis.

cyclonedx/model/component_evidence.py

Lines changed: 43 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
from decimal import Decimal
2121
from enum import Enum
2222
from json import loads as json_loads
23-
from typing import Any, Optional, Union
23+
from typing import Any, Optional, Union, Type
24+
from warnings import warn
2425
from xml.etree.ElementTree import Element as XmlElement # nosec B405
2526

2627
# See https://github.com/package-url/packageurl-python/issues/65
@@ -623,30 +624,6 @@ def __repr__(self) -> str:
623624
return f'<CallStack frames={len(self.frames)}>'
624625

625626

626-
class _IdentityRepositorySerializationHelper(serializable.helpers.BaseHelper):
627-
"""THIS CLASS IS NON-PUBLIC API"""
628-
629-
@classmethod
630-
def json_normalize(cls, o: SortedSet[Identity], *,
631-
view: Optional[type[serializable.ViewType]],
632-
**__: Any) -> Union[dict,list[dict],None]:
633-
if not o:
634-
return None
635-
if view and view is SchemaVersion1Dot5:
636-
# For Schema 1.5 JSON, return first identity as a single object
637-
first_identity = o[0]
638-
return json_loads(first_identity.as_json(view_=view)) # type: ignore[attr-defined]
639-
# For Schema 1.6 and others, return array of all identities
640-
return [json_loads(identity.as_json(view_=view)) for identity in o] # type: ignore[attr-defined]
641-
642-
@classmethod
643-
def json_denormalize(cls, o: Any, **__: Any) -> Optional[list[Identity]]:
644-
if isinstance(o, dict): # Single Identity object (Schema 1.5)
645-
return [Identity.from_json(o)] # type: ignore[attr-defined]
646-
elif isinstance(o, (list, tuple)): # Array of Identity objects (Schema 1.6)
647-
return [Identity.from_json(i) for i in o] # type: ignore[attr-defined]
648-
return None
649-
650627

651628
@serializable.serializable_class
652629
class ComponentEvidence:
@@ -677,7 +654,6 @@ def __init__(
677654
@serializable.view(SchemaVersion1Dot5)
678655
@serializable.view(SchemaVersion1Dot6)
679656
@serializable.xml_sequence(1)
680-
@serializable.type_mapping(_IdentityRepositorySerializationHelper)
681657
@serializable.xml_array(serializable.XmlArraySerializationType.FLAT, 'identity')
682658
# TODO: CDX 1.5 knows only one identity, all versions later known multiple ...
683659
# TODO: need to fix the serialization/normalization
@@ -774,3 +750,44 @@ def __hash__(self) -> int:
774750

775751
def __repr__(self) -> str:
776752
return f'<ComponentEvidence id={id(self)}>'
753+
754+
class _ComponentEvidenceSerializationHelper(serializable.helpers.BaseHelper):
755+
"""THIS CLASS IS NON-PUBLIC API"""
756+
757+
@classmethod
758+
def json_normalize(cls, o: ComponentEvidence, *,
759+
view: Optional[type[serializable.ViewType]],
760+
**__: Any) -> Union[dict,list[dict],None]:
761+
data:dict[str, Any] = json_loads( o.as_json(view))
762+
if view is SchemaVersion1Dot5:
763+
identities = data.get('identity', [])
764+
if il:=len(identities) > 1:
765+
warn(f'CycloneDX 1.5 does not support multiple identity items; dropping {il-1} items.')
766+
data['identity'] = identities[0]
767+
return data
768+
769+
@classmethod
770+
def json_denormalize(cls, o: dict[str, Any], **__: Any) -> Optional[list[Identity]]:
771+
return ComponentEvidence.from_json(o)
772+
773+
@classmethod
774+
def xml_normalize(cls, o: ComponentEvidence, *,
775+
element_name: str,
776+
view: Optional[Type['serializable.ViewType']],
777+
xmlns: Optional[str],
778+
**__: Any) -> Optional['XmlElement']:
779+
normalized: 'XmlElement' = o.as_xml(view, False, element_name, xmlns)
780+
if view is SchemaVersion1Dot5:
781+
identities = normalized.findall(f'./{{{xmlns}}}identity' if xmlns else './identity')
782+
if il:=len(identities) > 1:
783+
warn(f'CycloneDX 1.5 does not support multiple identity items; dropping {il-1} items.')
784+
for i in identities[1:]:
785+
normalized.remove(i)
786+
return normalized
787+
788+
@classmethod
789+
def xml_denormalize(cls, o: 'XmlElement', *,
790+
default_ns: Optional[str],
791+
**__: Any) -> Any:
792+
return ComponentEvidence.from_xml(o, default_ns)
793+

0 commit comments

Comments
 (0)