You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
-[Documenting your custom problems details](#documenting-your-custom-problems-details)
17
19
18
20
## Getting Started
19
21
@@ -178,14 +180,12 @@ curl -X POST http://localhost:8000/not-exist
178
180
}
179
181
```
180
182
181
-
## Unhandled errors handling
183
+
## Unexpected errors handling
182
184
183
-
Any unhandled errors raised during processing of a request will be automatically handled by the plugin which will returns an internal server error formatted as a Problem Details.
185
+
Any unexpected errors raised during processing of a request will be automatically handled by the plugin which will returns an internal server error formatted as a Problem Details.
184
186
185
187
> Also note that the exception will be logged as well using logger named `fastapi_problem_details.error_handlers`
186
188
187
-
The message of the error `str(exception)` will be used as the `detail` property and will default to a more generic message when not defined.
188
-
189
189
```python
190
190
from typing import Any
191
191
@@ -195,18 +195,14 @@ import fastapi_problem_details as problem
@@ -333,7 +321,7 @@ Note that in this example I've provided a custom `type` property but this might
333
321
334
322
> Likewise, truly generic problems -- i.e., conditions that might apply to any resource on the Web -- are usually better expressed as plain status codes. For example, a "write access disallowed" problem is probably unnecessary, since a 403 Forbidden status code in response to a PUT request is self-explanatory.
335
323
336
-
Also note that you can additional properties to the `ProblemResponse` object like `headers` or `instance`. Any extra properties will be added as-is in the returned Problem Details object (like the `user_id` in this example).
324
+
Also note that you can include additional properties to the `ProblemResponse` object like `headers` or `instance`. Any extra properties will be added as-is in the returned Problem Details object (like the `user_id` in this example).
337
325
338
326
Last but not least, any `null` values are stripped from returned Problem Details object.
339
327
@@ -390,7 +378,8 @@ curl http://localhost:8000 -v
390
378
"type":"about:blank",
391
379
"title":"Service Unavailable",
392
380
"status":503,
393
-
"detail":"One or several internal services are not working properly","service_1":"down",
381
+
"detail":"One or several internal services are not working properly",
382
+
"service_1":"down",
394
383
"service_2":"up"
395
384
}
396
385
```
@@ -399,7 +388,7 @@ The `ProblemException` exception takes almost same arguments as a `ProblemRespon
399
388
400
389
### Keeping the code DRY
401
390
402
-
If you start having to raise almost the same `ProblemException` in several places of your code (for example when you validate a requester permissions) you have too ways to avoid copy-pasting the same object in many places of your code
391
+
If you start having to raise almost the same `ProblemException` in several places of your code (for example when you validate a requester permissions) you have two ways to avoid copy-pasting the same object in many places of your code
The advantage of this solution is that its rather simple and straightforward. You do not have anything else to do to properly returns Problem Details responses.
433
422
434
-
The main issue of this is that it can cause your code to cross boundaries. If you start to use `ProblemException` into your domain logic, you couple your core code with your primary adapter (See ports and adapters pattern), your API. If you decide to build a CLI and/or and event based application using the same core logic, you'll end up with uncomfortable problem exception and status code which has no meaning here.
423
+
The main issue of this is that it can cause your code to cross boundaries. If you start to use `ProblemException` into your domain logic, you couple your core code with your HTTP API. If you decide to build a CLI and/or and event based application using the same core logic, you'll end up with uncomfortable problem exception and status code which has no meaning here.
The biggest advantage of this solution is that you decouple your core code from your FastAPI app. You can define regular Python exceptions whatever you want and just do the conversion for your API in your custom error handler(s).
485
474
486
475
The disadvantage obviously is that it requires you to write more code. Its a question of balance.
476
+
477
+
#### Wrapping up
478
+
479
+
Considering the two previous mechanisms, the way which worked best for me is to do the following:
480
+
481
+
- When I raise errors in my core (domain code, business logic) I use dedicated exceptions, unrelated to HTTP nor APIs, and I add a custom error handler to my FastAPI app to handle and returns a ProblemResponse`.
482
+
- When I want to raise an error directly in one of my API controller (i.e: a FastAPI route) I simply raise a `ProblemException`. If I'm raising same problem exception in several places I create a subclass of problem exception and put in my defaults and raise that error instead.
483
+
484
+
## Documenting your custom problems details
485
+
486
+
When registering problem details against your FastAPI app, it adds a `default` openapi response to all routes with the Problem Details schema. This might be enough in most cases but if you want to explicit additional problem details responses for specific status code or document additional properties you can register your Problem Details.
487
+
488
+
```python
489
+
from typing import Any, Literal
490
+
491
+
from fastapi import FastAPI, Request, status
492
+
493
+
import fastapi_problem_details as problem
494
+
from fastapi_problem_details import ProblemResponse
495
+
496
+
app = FastAPI()
497
+
problem.init_app(app)
498
+
499
+
500
+
classUserNotFoundProblem(problem.Problem):
501
+
status: Literal[404]
502
+
user_id: str
503
+
504
+
505
+
classUserNotFoundError(Exception):
506
+
def__init__(self, user_id: str) -> None:
507
+
super().__init__(f"There is no user with id {user_id!r}")
Note that this has limitation. Indeed, the `UserNotFoundProblem` class just act as a model schema for openapi documentation. You actually not instantiate this class and no validation is performed when returning the problem response. It means that the error handler can returns something which does not match a `UserNotFoundProblem`.
529
+
530
+
This is because of the way FastAPI manages errors. At the moment, there is no way to register error handler and its response schema in the same place and there is no mechanism to ensure both are synced.
0 commit comments