Skip to content

Commit 9890561

Browse files
committed
feat: iterate over validation errors
There was only one validation method, that returned a single error (and it was the first error (JSON) or the last one (XML)). The new function `SchemabasedValidator.iterate_errors()` allows to enumerate over all the validation errors. Signed-off-by: Krisztian Fekete <[email protected]>
1 parent d9da93d commit 9890561

File tree

5 files changed

+39
-1
lines changed

5 files changed

+39
-1
lines changed

cyclonedx/validation/__init__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818

1919
from abc import ABC, abstractmethod
20+
from collections.abc import Iterable
2021
from typing import TYPE_CHECKING, Any, Literal, Optional, Protocol, Union, overload
2122

2223
from ..schema import OutputFormat
@@ -120,6 +121,14 @@ def validate_str(self, data: str) -> Optional[ValidationError]:
120121
"""
121122
... # pragma: no cover
122123

124+
def iterate_errors(self, data: str) -> Iterable[ValidationError]:
125+
"""Validate a string, enumerating all the problems.
126+
127+
:param data: the data string to validate
128+
:return: iterator over the errors
129+
"""
130+
... # pragma: no cover
131+
123132

124133
class BaseSchemabasedValidator(ABC, SchemabasedValidator):
125134
"""Base Schema-based Validator"""

cyclonedx/validation/json.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
__all__ = ['JsonValidator', 'JsonStrictValidator']
2020

2121
from abc import ABC
22+
from collections.abc import Iterable
2223
from json import loads as json_loads
2324
from typing import TYPE_CHECKING, Any, Literal, Optional
2425

@@ -107,7 +108,14 @@ def __init__(self, schema_version: 'SchemaVersion') -> None:
107108
def validate_str(self, data: str) -> Optional[ValidationError]:
108109
raise self.__MDERROR[0] from self.__MDERROR[1]
109110

111+
def iterate_errors(self, data: str) -> Iterable[ValidationError]:
112+
raise self.__MDERROR[0] from self.__MDERROR[1]
110113
else:
114+
def iterate_errors(self, data: str) -> Iterable[ValidationError]:
115+
json_data = json_loads(data)
116+
validator = self._validator # may throw on error that MUST NOT be caught
117+
yield from validator.iter_errors(json_data)
118+
111119
def validate_str(self, data: str) -> Optional[ValidationError]:
112120
return self._validate_data(
113121
json_loads(data))

cyclonedx/validation/xml.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
__all__ = ['XmlValidator']
2020

2121
from abc import ABC
22+
from collections.abc import Iterable
2223
from typing import TYPE_CHECKING, Any, Literal, Optional
2324

2425
from ..exception import MissingOptionalDependencyException
@@ -53,12 +54,24 @@ def __init__(self, schema_version: 'SchemaVersion') -> None:
5354
# this is the def that is used for generating the documentation
5455
super().__init__(schema_version)
5556

56-
if _missing_deps_error:
57+
if _missing_deps_error: # noqa:C901
5758
__MDERROR = _missing_deps_error
5859

5960
def validate_str(self, data: str) -> Optional[ValidationError]:
6061
raise self.__MDERROR[0] from self.__MDERROR[1]
62+
63+
def iterate_errors(self, data: str) -> Iterable[ValidationError]:
64+
raise self.__MDERROR[0] from self.__MDERROR[1]
6165
else:
66+
def iterate_errors(self, data: str) -> Iterable[ValidationError]:
67+
xml_data = xml_fromstring( # nosec B320
68+
bytes(data, encoding='utf8'),
69+
parser=self.__xml_parser)
70+
validator = self._validator # may throw on error that MUST NOT be caught
71+
validator.validate(xml_data)
72+
for error in validator.error_log:
73+
yield ValidationError(error)
74+
6275
def validate_str(self, data: str) -> Optional[ValidationError]:
6376
return self._validate_data(
6477
xml_fromstring( # nosec B320

tests/test_validation_json.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ def test_validate_no_none(self, schema_version: SchemaVersion, test_data_file: s
117117
self.skipTest('MissingOptionalDependencyException')
118118
self.assertIsNone(validation_error)
119119

120+
self.assertEqual(list(validator.iterate_errors(test_data)), [])
121+
120122
@idata(chain(
121123
_dp_sv_tf(False),
122124
_dp_sv_own(False)
@@ -138,3 +140,5 @@ def test_validate_expected_error(self, schema_version: SchemaVersion, test_data_
138140

139141
squeezed_message = validation_error.get_squeezed_message(max_size=100)
140142
self.assertLessEqual(len(squeezed_message), 100, squeezed_message)
143+
144+
self.assertNotEqual(list(validator.iterate_errors(test_data)), [])

tests/test_validation_xml.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ def test_validate_no_none(self, schema_version: SchemaVersion, test_data_file: s
7777
self.skipTest('MissingOptionalDependencyException')
7878
self.assertIsNone(validation_error)
7979

80+
self.assertEqual(list(validator.iterate_errors(test_data)), [])
81+
8082
@idata(chain(
8183
_dp_sv_tf(False),
8284
_dp_sv_own(False)
@@ -98,3 +100,5 @@ def test_validate_expected_error(self, schema_version: SchemaVersion, test_data_
98100

99101
squeezed_message = validation_error.get_squeezed_message(max_size=100)
100102
self.assertLessEqual(len(squeezed_message), 100, squeezed_message)
103+
104+
self.assertNotEqual(list(validator.iterate_errors(test_data)), [])

0 commit comments

Comments
 (0)