Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 36 additions & 29 deletions ntia_conformance_checker/base_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@
from spdx_tools.spdx.validation.document_validator import validate_full_spdx_document

from .constants import DEFAULT_SBOM_SPEC
from .report import ReportContext, report_html, report_text
from .report import (
ReportContext,
get_validation_messages_json,
report_html,
report_text,
)
from .spdx3_utils import (
get_boms_from_spdx_document,
get_packages_from_bom,
Expand Down Expand Up @@ -856,13 +861,13 @@ def print_table_output(self, verbose: bool = False) -> None:
None
"""
report_context = ReportContext(
sbom_spec=self.sbom_spec,
compliance_standard=self.compliance_standard,
compliant=self.compliant,
requirement_results=self.table_elements,
components_without_info=self.all_components_without_info,
validation_messages=self.validation_messages,
parsing_error=self.parsing_error,
sbom_spec=getattr(self, "sbom_spec", ""),
compliance_standard=getattr(self, "compliance_standard", ""),
compliant=getattr(self, "compliant", False),
requirement_results=getattr(self, "table_elements", []),
components_without_info=getattr(self, "all_components_without_info", []),
validation_messages=getattr(self, "validation_messages", []),
parsing_error=getattr(self, "parsing_error", []),
)

print(report_text(report_context, verbose))
Expand All @@ -875,13 +880,13 @@ def output_html(self) -> str:
str: The HTML representation of the results.
"""
report_context = ReportContext(
sbom_spec=self.sbom_spec,
compliance_standard=self.compliance_standard,
compliant=self.compliant,
requirement_results=self.table_elements,
components_without_info=self.all_components_without_info,
validation_messages=self.validation_messages,
parsing_error=self.parsing_error,
sbom_spec=getattr(self, "sbom_spec", ""),
compliance_standard=getattr(self, "compliance_standard", ""),
compliant=getattr(self, "compliant", False),
requirement_results=getattr(self, "table_elements", []),
components_without_info=getattr(self, "all_components_without_info", []),
validation_messages=getattr(self, "validation_messages", []),
parsing_error=getattr(self, "parsing_error", []),
)

return report_html(report_context, verbose=True)
Expand All @@ -893,21 +898,23 @@ def output_json(self) -> Dict[str, Any]:
Subclasses may override to provide custom fields.
"""
result: Dict[str, Any] = {
"isConformant": self.compliant,
"isNtiaConformant": self.compliant, # backward compatibility
"complianceStandard": self.compliance_standard,
"sbomSpec": self.sbom_spec,
"validationMessages": (
list(map(str, self.validation_messages))
if self.validation_messages
else []
"isConformant": getattr(self, "compliant", False),
"isNtiaConformant": getattr(
self, "compliant", False
), # backward compatibility
"complianceStandard": getattr(self, "compliance_standard", ""),
"sbomSpec": getattr(self, "sbom_spec", ""),
"validationMessages": get_validation_messages_json(
getattr(self, "validation_messages", [])
),
"parsingError": getattr(self, "parsing_error", []),
"sbomName": getattr(self, "sbom_name", ""),
"specVersionProvided": getattr(self, "doc_version", False),
"authorNameProvided": getattr(self, "doc_author", False),
"timestampProvided": getattr(self, "doc_timestamp", False),
"dependencyRelationshipsProvided": getattr(
self, "dependency_relationships", False
),
"parsingError": self.parsing_error,
"sbomName": self.sbom_name,
"specVersionProvided": self.doc_version,
"authorNameProvided": self.doc_author,
"timestampProvided": self.doc_timestamp,
"dependencyRelationshipsProvided": self.dependency_relationships,
"totalNumberComponents": self.get_total_number_components(),
}

Expand Down
29 changes: 28 additions & 1 deletion ntia_conformance_checker/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from __future__ import annotations

from dataclasses import dataclass
from typing import TYPE_CHECKING, List, Optional, Tuple
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple

from .constants import (
SUPPORTED_COMPLIANCE_STANDARDS,
Expand Down Expand Up @@ -120,6 +120,33 @@ def get_validation_messages_html(
return html


def get_validation_messages_json(
validation_messages: List[ValidationMessage],
) -> List[Dict[str, str]]:
"""Generates JSON-serializable list for validation messages and context details.

Args:
validation_messages (List[ValidationMessage]): List of validation messages.

Returns:
List[Dict[str, str]]: JSON-serializable representation of the validation messages.
"""
json_output: List[Dict[str, str]] = []

for msg in validation_messages:
if not getattr(msg, "validation_message", None):
continue
val_msg = {"message": msg.validation_message}
if getattr(msg, "context", None):
ctx = msg.context
val_msg["spdxId"] = str(getattr(ctx, "spdx_id", ""))
val_msg["parentId"] = str(getattr(ctx, "parent_id", ""))
val_msg["elementType"] = str(getattr(ctx, "element_type", ""))
json_output.append(val_msg)

return json_output


def report_text(
rc: ReportContext,
verbose: bool = False,
Expand Down
11 changes: 11 additions & 0 deletions tests/test_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@ def test_sbomchecker_fsct3_spdx3_no_elements_missing():
assert sbom.doc is not None
assert isinstance(sbom.doc, spdx3.SHACLObjectSet)
assert sbom.compliant
assert len(sbom.validation_messages) == 0


def test_sbomchecker_spdx3_missing_supplier_name():
Expand All @@ -370,6 +371,7 @@ def test_sbomchecker_spdx3_missing_supplier_name():
assert sbom.doc is not None
assert isinstance(sbom.doc, spdx3.SHACLObjectSet)
assert len(sbom.components_without_suppliers) == 1
assert len(sbom.validation_messages) == 0


def test_sbomchecker_spdx3_missing_version():
Expand All @@ -379,6 +381,7 @@ def test_sbomchecker_spdx3_missing_version():
assert sbom.doc is not None
assert isinstance(sbom.doc, spdx3.SHACLObjectSet)
assert len(sbom.components_without_versions) == 1
assert len(sbom.validation_messages) == 0


def test_sbomchecker_spdx3_missing_unique_identifiers():
Expand All @@ -403,6 +406,7 @@ def test_sbomchecker_output_json():
assert got["sbomName"] == "xyz-0.1.0"
assert not got["isNtiaConformant"]
assert not got["isConformant"]
assert not got["validationMessages"]
assert got["authorNameProvided"]
assert got["timestampProvided"]
assert got["dependencyRelationshipsProvided"]
Expand All @@ -421,6 +425,13 @@ def test_sbomchecker_output_json():
assert got["totalNumberComponents"] == 3


def test_sbomchecker_output_json_validation_messages():
test_file = Path(__file__).parent / "data" / "spdx3" / "has_no_sbom.json"
sbom = sbom_checker.SbomChecker(str(test_file), sbom_spec="spdx3")
got = sbom.output_json()
assert got["validationMessages"] # Should be a validation error about rootElement


def test_sbomchecker_output_html():
filepath = os.path.join(
os.path.dirname(__file__), "data", "other_tests", "SPDXSBOMExample.spdx.yml"
Expand Down
Loading