Skip to content

Commit e79e9a4

Browse files
committed
doc: answer a SCIM error to a query parameter FastAPI refuses
The guide has FastAPI validate the query parameters of a collection, which raises RequestValidationError rather than the ValidationError the error handlers were registered for, so a malformed count, or an attributes or sortBy path that does not resolve, answered the FastAPI 422 body and threw away a scimType that was already computed.
1 parent 2550e8b commit e79e9a4

3 files changed

Lines changed: 33 additions & 1 deletion

File tree

‎doc/guides/_examples/fastapi_example.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from fastapi import Query
1010
from fastapi import Request
1111
from fastapi import Response
12+
from fastapi.exceptions import RequestValidationError
1213
from fastapi.responses import JSONResponse
1314
from pydantic import ValidationError
1415

@@ -104,6 +105,7 @@ def resolve_user(user_id: str):
104105

105106
# -- error-handlers-start --
106107
@app.exception_handler(ValidationError)
108+
@app.exception_handler(RequestValidationError)
107109
async def handle_validation_error(request, error):
108110
"""Turn Pydantic validation errors into SCIM error responses."""
109111
scim_error = Error.from_validation_error(error.errors()[0])

‎doc/guides/fastapi.rst‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,9 @@ validation errors, HTTP exceptions, and application errors aligned with SCIM res
6969

7070
``handle_validation_error`` catches the :class:`~pydantic_core.ValidationError` raised by
7171
:meth:`~scim2_models.BaseModel.model_validate` and returns a SCIM :class:`~scim2_models.Error`
72-
response.
72+
response. It is registered for :class:`~fastapi.exceptions.RequestValidationError` as well, which
73+
is what FastAPI raises when it validates the query parameters itself: without it a malformed
74+
``count`` or ``attributes`` answers the FastAPI ``422`` body instead of a SCIM error.
7375
``handle_http_exception`` catches HTTP errors such as the 404 raised by the dependency and wraps
7476
them in a SCIM :class:`~scim2_models.Error`.
7577
``handle_scim_error`` catches any :class:`~scim2_models.SCIMException` (uniqueness, mutability, …)

‎tests/test_doc_examples.py‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,3 +409,31 @@ def test_fastapi_example_smoke():
409409
)
410410
assert put_response.status_code == 200
411411
assert put_response.json()["displayName"] == "Barbara J."
412+
413+
414+
@pytest.mark.parametrize(
415+
("parameters", "scim_type"),
416+
[
417+
({"count": "abc"}, "invalidSyntax"),
418+
({"attributes": 'emails[type eq "work"]'}, "invalidPath"),
419+
],
420+
)
421+
def test_fastapi_example_answers_a_scim_error_to_a_refused_query_parameter(
422+
parameters, scim_type
423+
):
424+
"""FastAPI validates the query parameters itself, so a refused one never reaches the endpoint.
425+
426+
The failure is a RequestValidationError, which the guide handles next to
427+
ValidationError: without it the framework answers its own body, and a
428+
client reading the scimType of the error finds none.
429+
"""
430+
from starlette.testclient import TestClient
431+
432+
from doc.guides._examples.fastapi_example import app
433+
434+
client = TestClient(app)
435+
436+
response = client.get("/scim/v2/Users", params=parameters)
437+
assert response.status_code == 400
438+
assert response.headers["Content-Type"] == "application/scim+json"
439+
assert response.json()["scimType"] == scim_type

0 commit comments

Comments
 (0)