Skip to content

Commit 5a842e1

Browse files
committed
fix: correct validation errors and refactor for code quality
- Fix validation_errors.message -> validation_errors.data.message (ValidationError wraps underlying error in data attribute) - Use consistent input_format variable naming throughout (semantic clarity: detecting input format, not output) - Fix XML validator variable name to avoid type conflicts - Refactor validate_sbom to reduce complexity (C901) - Extract _detect_json_format helper function - Extract _detect_xml_format helper function - Improves maintainability and testability - Add type ignore for lxml import (missing stubs) - Simplify docstring per maintainer feedback - Change 'SBOMs' to 'documents' (CycloneDX not SBOM-only) Addresses all maintainer feedback from PR review. Fixes test failures and passes all pre-commit checks. Signed-off-by: Saquib Saifee <saquibsaifee2@gmail.com>
1 parent d3b3f54 commit 5a842e1

1 file changed

Lines changed: 47 additions & 39 deletions

File tree

examples/complex_validation.py

Lines changed: 47 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,8 @@
2828
from cyclonedx.validation.xml import XmlValidator
2929

3030
"""
31-
This example demonstrates how to validate CycloneDX SBOMs (both JSON and XML).
32-
Validation is built upon `jsonschema` for JSON and `lxml` for XML.
33-
To use validation, ensure you have installed the library with the validation extra:
34-
pip install cyclonedx-python-lib[validation]
35-
or
36-
pip install cyclonedx-python-lib[json-validation,xml-validation]
31+
This example demonstrates how to validate CycloneDX documents (both JSON and XML).
32+
Make sure to have the needed dependencies installed - install the library's extra 'validation' for that.
3733
"""
3834

3935
# region Sample SBOMs
@@ -100,7 +96,7 @@
10096
validation_errors = json_validator.validate_str(INVALID_JSON_SBOM)
10197
if validation_errors:
10298
print('Validation failed as expected.')
103-
print(f'Error Message: {validation_errors.message}')
99+
print(f'Error Message: {validation_errors.data.message}')
104100
print(f'JSON Path: {validation_errors.data.json_path}')
105101
print(f'Invalid Data: {validation_errors.data.instance}')
106102
except MissingOptionalDependencyException as error:
@@ -119,8 +115,8 @@
119115
xml_validator: 'XmlValidator' = make_schemabased_validator(OutputFormat.XML, SchemaVersion.V1_5)
120116

121117
try:
122-
validation_errors = xml_validator.validate_str(XML_SBOM)
123-
if validation_errors:
118+
xml_validation_errors = xml_validator.validate_str(XML_SBOM)
119+
if xml_validation_errors:
124120
print('XML SBOM is invalid!', file=sys.stderr)
125121
else:
126122
print('XML SBOM is valid')
@@ -138,51 +134,63 @@
138134
print('--- Dynamic Validation ---')
139135

140136

141-
def validate_sbom(raw_data: str) -> bool:
142-
"""Validate an SBOM by detecting its format and version."""
143-
144-
# 1. Attempt to detect JSON and its version
137+
def _detect_json_format(raw_data: str) -> tuple[OutputFormat, SchemaVersion] | None:
138+
"""Detect JSON format and extract schema version."""
145139
try:
146140
data = json.loads(raw_data)
147-
input_format = OutputFormat.JSON
148141
spec_version_str = data.get('specVersion')
149142
if not spec_version_str:
150143
print('Error: Missing specVersion in JSON SBOM', file=sys.stderr)
151-
return False
144+
return None
152145
schema_version = SchemaVersion.from_version(spec_version_str)
146+
return (OutputFormat.JSON, schema_version)
153147
except (json.JSONDecodeError, ValueError):
154-
# 2. Attempt to detect XML and its version
155-
try:
156-
from lxml import etree
157-
xml_tree = etree.fromstring(raw_data.encode('utf-8'))
158-
output_format = OutputFormat.XML
159-
# Extract version from CycloneDX namespace
160-
schema_version = SchemaVersion.V1_5 # Default
161-
for ns in xml_tree.nsmap.values():
162-
if ns and ns.startswith('http://cyclonedx.org/schema/bom/'):
163-
try:
164-
schema_version = SchemaVersion.from_version(ns.split('/')[-1])
165-
break
166-
except ValueError:
167-
pass
168-
except (ImportError, etree.XMLSyntaxError):
169-
print('Error: Unknown or malformed SBOM format', file=sys.stderr)
170-
return False
171-
except Exception as e:
172-
print(f'Error: Format detection failed: {e}', file=sys.stderr)
173-
return False
148+
return None
149+
150+
151+
def _detect_xml_format(raw_data: str) -> tuple[OutputFormat, SchemaVersion] | None:
152+
"""Detect XML format and extract schema version."""
153+
try:
154+
from lxml import etree # type: ignore[import-untyped]
155+
xml_tree = etree.fromstring(raw_data.encode('utf-8'))
156+
# Extract version from CycloneDX namespace
157+
schema_version = SchemaVersion.V1_5 # Default
158+
for ns in xml_tree.nsmap.values():
159+
if ns and ns.startswith('http://cyclonedx.org/schema/bom/'):
160+
try:
161+
schema_version = SchemaVersion.from_version(ns.split('/')[-1])
162+
break
163+
except ValueError:
164+
pass
165+
return (OutputFormat.XML, schema_version)
166+
except (ImportError, etree.XMLSyntaxError):
167+
print('Error: Unknown or malformed SBOM format', file=sys.stderr)
168+
return None
169+
except Exception as e:
170+
print(f'Error: Format detection failed: {e}', file=sys.stderr)
171+
return None
172+
173+
174+
def validate_sbom(raw_data: str) -> bool:
175+
"""Validate an SBOM by detecting its format and version."""
176+
# Detect format and version
177+
format_info = _detect_json_format(raw_data) or _detect_xml_format(raw_data)
178+
if not format_info:
179+
return False
180+
181+
input_format, schema_version = format_info
174182

175-
# 3. Perform validation using the detected format and version
183+
# Perform validation
176184
try:
177-
validator = make_schemabased_validator(output_format, schema_version)
185+
validator = make_schemabased_validator(input_format, schema_version)
178186
errors = validator.validate_str(raw_data)
179187

180188
if errors:
181-
print(f'Validation failed for {output_format.name} version {schema_version.to_version()}', file=sys.stderr)
189+
print(f'Validation failed for {input_format.name} version {schema_version.to_version()}', file=sys.stderr)
182190
print(f'Reason: {errors}', file=sys.stderr)
183191
return False
184192

185-
print(f'Successfully validated {output_format.name} SBOM (Version {schema_version.to_version()})')
193+
print(f'Successfully validated {input_format.name} SBOM (Version {schema_version.to_version()})')
186194
return True
187195

188196
except MissingOptionalDependencyException as error:

0 commit comments

Comments
 (0)