Skip to content

Commit 06d463c

Browse files
committed
refactor: errors and exceptions overhaul
1 parent a1697b9 commit 06d463c

File tree

13 files changed

+955
-139
lines changed

13 files changed

+955
-139
lines changed

doc/changelog.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ Added
99
- Resources define their schema URN with a ``__schema__`` classvar instead of a ``schemas`` default value. :issue:`110`
1010
- Validation that the base schema is present in ``schemas`` during SCIM context validation.
1111
- Validation that extension schemas are known during SCIM context validation.
12+
- Introduce SCIM exceptions hierarchy (:class:`~scim2_models.SCIMException` and subclasses) corresponding to RFC 7644 error types. :issue:`103`
13+
- :meth:`Error.from_validation_error <scim2_models.Error.from_validation_error>` to convert Pydantic :class:`~pydantic.ValidationError` to SCIM :class:`~scim2_models.Error`.
1214

1315
Changed
1416
^^^^^^^
@@ -17,6 +19,7 @@ Changed
1719
Deprecated
1820
^^^^^^^^^^
1921
- Defining ``schemas`` with a default value is deprecated. Use ``__schema__ = URN("...")`` instead.
22+
- ``Error.make_*_error()`` methods are deprecated. Use ``<Exception>.to_error()`` instead.
2023

2124
[0.5.2] - 2026-01-22
2225
--------------------

doc/tutorial.rst

Lines changed: 66 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -275,26 +275,79 @@ Extensions attributes are accessed with brackets, e.g. ``user[EnterpriseUser].em
275275
... }
276276
277277
278-
Pre-defined Error objects
279-
=========================
278+
Errors and Exceptions
279+
=====================
280280

281-
:rfc:`RFC7643 §3.12 <7643#section-3.12>` pre-defined errors are usable.
281+
scim2-models provides a hierarchy of exceptions corresponding to :rfc:`RFC7644 §3.12 <7644#section-3.12>` error types.
282+
Each exception can be converted to an :class:`~scim2_models.Error` response object or used in Pydantic validators.
283+
284+
Raising exceptions
285+
~~~~~~~~~~~~~~~~~~
286+
287+
Exceptions are named after their ``scimType`` value:
282288

283289
.. code-block:: python
284290
285-
>>> from scim2_models import Error
291+
>>> from scim2_models import InvalidPathException, PathNotFoundException
286292
287-
>>> error = Error.make_invalid_path_error()
288-
>>> dump = error.model_dump()
289-
>>> assert dump == {
290-
... 'detail': 'The "path" attribute was invalid or malformed (see Figure 7 of RFC7644).',
291-
... 'schemas': ['urn:ietf:params:scim:api:messages:2.0:Error'],
292-
... 'scimType': 'invalidPath',
293-
... 'status': '400'
294-
... }
293+
>>> raise InvalidPathException(path="invalid..path")
294+
Traceback (most recent call last):
295+
...
296+
scim2_models.exceptions.InvalidPathException: The path attribute was invalid or malformed
297+
298+
>>> raise PathNotFoundException(path="unknownAttr")
299+
Traceback (most recent call last):
300+
...
301+
scim2_models.exceptions.PathNotFoundException: The specified path references a non-existent field
302+
303+
Converting to Error response
304+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
305+
306+
Use :meth:`~scim2_models.SCIMException.to_error` to convert an exception to an :class:`~scim2_models.Error` response:
307+
308+
.. code-block:: python
309+
310+
>>> from scim2_models import InvalidPathException
311+
312+
>>> exc = InvalidPathException(path="invalid..path")
313+
>>> error = exc.to_error()
314+
>>> error.status
315+
400
316+
>>> error.scim_type
317+
'invalidPath'
295318
296-
The exhaustive list is available in the :class:`reference <scim2_models.Error>`.
319+
Converting from ValidationError
320+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
297321

322+
Use :meth:`Error.from_validation_error <scim2_models.Error.from_validation_error>` to convert a single Pydantic error to an :class:`~scim2_models.Error`:
323+
324+
.. code-block:: python
325+
326+
>>> from pydantic import ValidationError
327+
>>> from scim2_models import Error, User
328+
>>> from scim2_models.base import Context
329+
330+
>>> try:
331+
... User.model_validate({"userName": None}, context={"scim": Context.RESOURCE_CREATION_REQUEST})
332+
... except ValidationError as exc:
333+
... error = Error.from_validation_error(exc.errors()[0])
334+
>>> error.scim_type
335+
'invalidValue'
336+
337+
Use :meth:`Error.from_validation_errors <scim2_models.Error.from_validation_errors>` to convert all errors at once:
338+
339+
.. code-block:: python
340+
341+
>>> try:
342+
... User.model_validate({"userName": 123, "displayName": 456})
343+
... except ValidationError as exc:
344+
... errors = Error.from_validation_errors(exc)
345+
>>> len(errors)
346+
2
347+
>>> [e.detail for e in errors]
348+
['Input should be a valid string: userName', 'Input should be a valid string: displayName']
349+
350+
The exhaustive list of exceptions is available in the :class:`reference <scim2_models.SCIMException>`.
298351

299352
Custom models
300353
=============

scim2_models/__init__.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,18 @@
77
from .attributes import MultiValuedComplexAttribute
88
from .base import BaseModel
99
from .context import Context
10+
from .exceptions import InvalidFilterException
11+
from .exceptions import InvalidPathException
12+
from .exceptions import InvalidSyntaxException
13+
from .exceptions import InvalidValueException
14+
from .exceptions import InvalidVersionException
15+
from .exceptions import MutabilityException
16+
from .exceptions import NoTargetException
17+
from .exceptions import PathNotFoundException
18+
from .exceptions import SCIMException
19+
from .exceptions import SensitiveException
20+
from .exceptions import TooManyException
21+
from .exceptions import UniquenessException
1022
from .messages.bulk import BulkOperation
1123
from .messages.bulk import BulkRequest
1224
from .messages.bulk import BulkResponse
@@ -17,10 +29,7 @@
1729
from .messages.patch_op import PatchOperation
1830
from .messages.search_request import SearchRequest
1931
from .path import URN
20-
from .path import InvalidPathError
2132
from .path import Path
22-
from .path import PathError
23-
from .path import PathNotFoundError
2433
from .reference import ExternalReference
2534
from .reference import Reference
2635
from .reference import URIReference
@@ -84,17 +93,22 @@
8493
"GroupMember",
8594
"GroupMembership",
8695
"Im",
87-
"InvalidPathError",
96+
"InvalidFilterException",
97+
"InvalidPathException",
98+
"InvalidSyntaxException",
99+
"InvalidValueException",
100+
"InvalidVersionException",
88101
"ListResponse",
89102
"Manager",
90103
"Message",
91104
"Meta",
92105
"Mutability",
106+
"MutabilityException",
93107
"MultiValuedComplexAttribute",
94108
"Name",
109+
"NoTargetException",
95110
"Path",
96-
"PathError",
97-
"PathNotFoundError",
111+
"PathNotFoundException",
98112
"Patch",
99113
"PatchOp",
100114
"PatchOperation",
@@ -106,12 +120,16 @@
106120
"ResourceType",
107121
"Returned",
108122
"Role",
123+
"SCIMException",
109124
"Schema",
110125
"SchemaExtension",
111126
"SearchRequest",
127+
"SensitiveException",
112128
"ServiceProviderConfig",
113129
"Sort",
130+
"TooManyException",
114131
"Uniqueness",
132+
"UniquenessException",
115133
"URIReference",
116134
"URN",
117135
"User",

0 commit comments

Comments
 (0)