Skip to content

Commit 21493e1

Browse files
committed
chore: Improve exceptions and introduce a new one for invalid values
Signed-off-by: Tudor Plugaru <plugaru.tudor@protonmail.com>
1 parent 1d43d68 commit 21493e1

File tree

3 files changed

+97
-106
lines changed

3 files changed

+97
-106
lines changed

src/cloudevents/core/v1/event.py

Lines changed: 35 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
CloudEventValidationError,
2323
CustomExtensionAttributeError,
2424
InvalidAttributeTypeError,
25+
InvalidAttributeValueError,
2526
MissingRequiredAttributeError,
2627
)
2728

@@ -84,41 +85,37 @@ def _validate_required_attributes(
8485
errors = defaultdict(list)
8586

8687
if "id" not in attributes:
87-
errors["id"].append(MissingRequiredAttributeError(missing="id"))
88+
errors["id"].append(MissingRequiredAttributeError(attribute_name="id"))
8889
if attributes.get("id") is None:
8990
errors["id"].append(
90-
InvalidAttributeTypeError("Attribute 'id' must not be None")
91+
InvalidAttributeValueError("id", "Attribute 'id' must not be None")
9192
)
9293
if not isinstance(attributes.get("id"), str):
93-
errors["id"].append(
94-
InvalidAttributeTypeError("Attribute 'id' must be a string")
95-
)
94+
errors["id"].append(InvalidAttributeTypeError("id", str))
9695

9796
if "source" not in attributes:
98-
errors["source"].append(MissingRequiredAttributeError(missing="source"))
99-
if not isinstance(attributes.get("source"), str):
10097
errors["source"].append(
101-
InvalidAttributeTypeError("Attribute 'source' must be a string")
98+
MissingRequiredAttributeError(attribute_name="source")
10299
)
100+
if not isinstance(attributes.get("source"), str):
101+
errors["source"].append(InvalidAttributeTypeError("source", str))
103102

104103
if "type" not in attributes:
105-
errors["type"].append(MissingRequiredAttributeError(missing="type"))
104+
errors["type"].append(MissingRequiredAttributeError(attribute_name="type"))
106105
if not isinstance(attributes.get("type"), str):
107-
errors["type"].append(
108-
InvalidAttributeTypeError("Attribute 'type' must be a string")
109-
)
106+
errors["type"].append(InvalidAttributeTypeError("type", str))
110107

111108
if "specversion" not in attributes:
112109
errors["specversion"].append(
113-
MissingRequiredAttributeError(missing="specversion")
110+
MissingRequiredAttributeError(attribute_name="specversion")
114111
)
115112
if not isinstance(attributes.get("specversion"), str):
116-
errors["specversion"].append(
117-
InvalidAttributeTypeError("Attribute 'specversion' must be a string")
118-
)
113+
errors["specversion"].append(InvalidAttributeTypeError("specversion", str))
119114
if attributes.get("specversion") != "1.0":
120115
errors["specversion"].append(
121-
InvalidAttributeTypeError("Attribute 'specversion' must be '1.0'")
116+
InvalidAttributeValueError(
117+
"specversion", "Attribute 'specversion' must be '1.0'"
118+
)
122119
)
123120
return errors
124121

@@ -136,46 +133,43 @@ def _validate_optional_attributes(
136133

137134
if "time" in attributes:
138135
if not isinstance(attributes["time"], datetime):
139-
errors["time"].append(
140-
InvalidAttributeTypeError(
141-
"Attribute 'time' must be a datetime object"
142-
)
143-
)
136+
errors["time"].append(InvalidAttributeTypeError("time", datetime))
144137
if hasattr(attributes["time"], "tzinfo") and not attributes["time"].tzinfo:
145138
errors["time"].append(
146-
InvalidAttributeTypeError("Attribute 'time' must be timezone aware")
139+
InvalidAttributeValueError(
140+
"time", "Attribute 'time' must be timezone aware"
141+
)
147142
)
148143
if "subject" in attributes:
149144
if not isinstance(attributes["subject"], str):
150-
errors["subject"].append(
151-
InvalidAttributeTypeError("Attribute 'subject' must be a string")
152-
)
145+
errors["subject"].append(InvalidAttributeTypeError("subject", str))
153146
if not attributes["subject"]:
154147
errors["subject"].append(
155-
InvalidAttributeTypeError("Attribute 'subject' must not be empty")
148+
InvalidAttributeValueError(
149+
"subject", "Attribute 'subject' must not be empty"
150+
)
156151
)
157152
if "datacontenttype" in attributes:
158153
if not isinstance(attributes["datacontenttype"], str):
159154
errors["datacontenttype"].append(
160-
InvalidAttributeTypeError(
161-
"Attribute 'datacontenttype' must be a string"
162-
)
155+
InvalidAttributeTypeError("datacontenttype", str)
163156
)
164157
if not attributes["datacontenttype"]:
165158
errors["datacontenttype"].append(
166-
InvalidAttributeTypeError(
167-
"Attribute 'datacontenttype' must not be empty"
159+
InvalidAttributeValueError(
160+
"datacontenttype",
161+
"Attribute 'datacontenttype' must not be empty",
168162
)
169163
)
170164
if "dataschema" in attributes:
171165
if not isinstance(attributes["dataschema"], str):
172166
errors["dataschema"].append(
173-
InvalidAttributeTypeError("Attribute 'dataschema' must be a string")
167+
InvalidAttributeTypeError("dataschema", str)
174168
)
175169
if not attributes["dataschema"]:
176170
errors["dataschema"].append(
177-
InvalidAttributeTypeError(
178-
"Attribute 'dataschema' must not be empty"
171+
InvalidAttributeValueError(
172+
"dataschema", "Attribute 'dataschema' must not be empty"
179173
)
180174
)
181175
return errors
@@ -200,19 +194,22 @@ def _validate_extension_attributes(
200194
if extension_attribute == "data":
201195
errors[extension_attribute].append(
202196
CustomExtensionAttributeError(
203-
"Extension attribute 'data' is reserved and must not be used"
197+
extension_attribute,
198+
"Extension attribute 'data' is reserved and must not be used",
204199
)
205200
)
206201
if not (1 <= len(extension_attribute) <= 20):
207202
errors[extension_attribute].append(
208203
CustomExtensionAttributeError(
209-
f"Extension attribute '{extension_attribute}' should be between 1 and 20 characters long"
204+
extension_attribute,
205+
f"Extension attribute '{extension_attribute}' should be between 1 and 20 characters long",
210206
)
211207
)
212208
if not re.match(r"^[a-z0-9]+$", extension_attribute):
213209
errors[extension_attribute].append(
214210
CustomExtensionAttributeError(
215-
f"Extension attribute '{extension_attribute}' should only contain lowercase letters and numbers"
211+
extension_attribute,
212+
f"Extension attribute '{extension_attribute}' should only contain lowercase letters and numbers",
216213
)
217214
)
218215
return errors

src/cloudevents/core/v1/exceptions.py

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
# License for the specific language governing permissions and limitations
1313
# under the License.
1414
class BaseCloudEventException(Exception):
15+
"""A CloudEvent generic exception."""
16+
1517
pass
1618

1719

@@ -25,7 +27,7 @@ def __init__(self, errors: dict[str, list[BaseCloudEventException]]) -> None:
2527
:param errors: The errors gathered during the CloudEvent creation where key
2628
is the name of the attribute and value is a list of errors related to that attribute.
2729
"""
28-
super().__init__("Validation errors occurred")
30+
super().__init__("Failed to create CloudEvent due to the validation errors:")
2931
self.errors: dict[str, list[BaseCloudEventException]] = errors
3032

3133
def __str__(self) -> str:
@@ -35,22 +37,40 @@ def __str__(self) -> str:
3537
return f"{super().__str__()}: {', '.join(error_messages)}"
3638

3739

38-
class MissingRequiredAttributeError(BaseCloudEventException):
40+
class MissingRequiredAttributeError(BaseCloudEventException, ValueError):
41+
"""
42+
Raised for attributes that are required to be present by the specification.
43+
"""
44+
45+
def __init__(self, attribute_name: str) -> None:
46+
super().__init__(f"Missing required attribute: '{attribute_name}'")
47+
48+
49+
class CustomExtensionAttributeError(BaseCloudEventException, ValueError):
3950
"""
40-
Exception for missing required attribute.
51+
Raised when a custom extension attribute violates naming conventions.
4152
"""
4253

43-
def __init__(self, missing: str) -> None:
44-
super().__init__(f"Missing required attribute: '{missing}'")
54+
def __init__(self, extension_attribute: str, msg: str) -> None:
55+
self.extension_attribute = extension_attribute
56+
super().__init__(msg)
4557

4658

47-
class CustomExtensionAttributeError(BaseCloudEventException):
59+
class InvalidAttributeTypeError(BaseCloudEventException, TypeError):
4860
"""
49-
Exception for invalid custom extension names.
61+
Raised when an attribute has an unsupported type.
5062
"""
5163

64+
def __init__(self, attribute_name: str, expected_type: type) -> None:
65+
self.attribute_name = attribute_name
66+
super().__init__(f"Attribute '{attribute_name}' must be a {expected_type}")
5267

53-
class InvalidAttributeTypeError(BaseCloudEventException):
68+
69+
class InvalidAttributeValueError(BaseCloudEventException, ValueError):
5470
"""
55-
Exception for invalid attribute type.
71+
Raised when an attribute has an invalid value.
5672
"""
73+
74+
def __init__(self, attribute_name: str, msg: str) -> None:
75+
self.attribute_name = attribute_name
76+
super().__init__(msg)

0 commit comments

Comments
 (0)