Error handling

← Error handling

Ashterix

docs: rewrite JsonRpcBundle docs, add SDK docs, move them under docs/api

en/docs/api/rest-bundle/error_handling.md new file +94 āˆ’0

1 + # Error handling
2 +
3 + ## Format
4 +
5 + Every error of a REST call or document is returned as an [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457) (problem details) document with the HTTP status of the error:
6 +
7 + ```json
8 + {
9 + "_error_object_standard": "RFC 9457",
10 + "status": 400,
11 + "type": "about:blank",
12 + "instance": "/rest/catalog/items/42",
13 + "title": "Ufo\\RestBundle\\Exceptions\\RestBadRequestException",
14 + "detail": "Invalid Data for call method: catalog.update",
15 + "detail-data": {
16 + "title": ["This value is too short. It should have 3 characters or more."]
17 + }
18 + }
19 + ```
20 +
21 + | Field | Value |
22 + | --- | --- |
23 + | `status` | the HTTP status, the same as the response status |
24 + | `type` | always `about:blank` |
25 + | `instance` | the request path |
26 + | `title` | the REST exception class |
27 + | `detail` | the error message |
28 + | `detail-data` | only if the RPC error contains an array of data: per-parameter messages for validation and type errors |
29 +
30 + Stack traces are not included in the document.
31 +
32 + ## How the status is chosen
33 +
34 + The RPC core turns any method exception into an RPC error (the `rpc.error` event): exceptions from the `ufo-tech/rpc-exceptions` package keep their class and code, an argument `TypeError` becomes a parameter error, other exceptions are wrapped ([Error handling in JsonRpcBundle](/docs/api/json-rpc-bundle/error_handling)). The REST layer recreates the exception from the RPC error and converts it with the `RestErrorNormalizer` class:
35 +
36 + | RPC exception | REST exception | Status |
37 + | --- | --- | --- |
38 + | `RpcJsonParseException` | `RestBadRequestException` | 400 |
39 + | `RpcMethodNotFoundExceptionRpc` | `RestResourceNotFoundException` | 404 |
40 + | `ConstraintsImposedException` | `RestValidationException` | 422 |
41 + | `RpcBadParamException` (parameter validation and type errors) | `RestBadRequestException` | 400 |
42 + | `RpcBadRequestException` | `RestBadRequestException` | 400 |
43 + | `RpcInternalException`, `RpcRuntimeException` | `RestInternalServerErrorException` | 500 |
44 + | `RpcLogicException` | `RestBadRequestException` | 400 |
45 + | `RpcTokenNotSentException`, `RpcInvalidTokenException` | `RestUnauthorizedException` | 401 |
46 + | `RpcDataNotFoundException` | `RestResourceNotFoundException` | 404 |
47 + | `CustomApplicationException` | `RestApplicationException` | the exception code if it is an HTTP error code, otherwise 400 |
48 + | any other | `RestBadRequestException` | the exception code if it is an HTTP error code, otherwise 400 |
49 +
50 + The table is checked top to bottom with `instanceof`, so subclasses come before their parent classes. An HTTP error code is a code from the `RestErrorNormalizer::REST_ERROR_CODES` list (standard 4xx and 5xx codes). If an exception from any row of the table has such a code, the status is taken from it.
51 +
52 + :::info
53 + The `404` row for `RpcMethodNotFoundExceptionRpc`, the `422` row for `ConstraintsImposedException` and keeping the HTTP code of an application error are available since version 1.3. In 1.0–1.2 these cases returned `400`. See [Fixed issues](/docs/api/rest-bundle/developers/known_issues/fixed).
54 + :::
55 +
56 + ## REST layer errors
57 +
58 + | Situation | Exception | Status |
59 + | --- | --- | --- |
60 + | no REST route for the path, unknown version, a method of another group | `RestResourceNotFoundException` | 404 |
61 + | the HTTP method is not allowed for the route | `RestResourceNotAllowedException` | 405 |
62 + | invalid or scalar JSON body | `RestBadRequestException` | 400 |
63 + | token not sent or invalid | `RestUnauthorizedException` | 401 |
64 +
65 + ## Returning a specific status from a method
66 +
67 + Throw an RPC exception from the table or an exception with an HTTP code:
68 +
69 + ```php
70 + use Ufo\RpcError\CustomApplicationException;
71 + use Ufo\RpcError\RpcDataNotFoundException;
72 +
73 + /**
74 + * @throws RpcDataNotFoundException
75 + * @throws CustomApplicationException
76 + */
77 + #[Route('/items/{id}', name: 'item', methods: ['GET'])]
78 + public function item(int $id): array
79 + {
80 + $item = $this->repository->find($id) ?? throw new RpcDataNotFoundException('Item is not found'); // 404
81 + if ($item->isClosed()) {
82 + throw new CustomApplicationException('Item is closed', 409); // 409
83 + }
84 + return $item->toArray();
85 + }
86 + ```
87 +
88 + Via JSON-RPC the same exceptions produce ordinary RPC errors, so one method serves both protocols. The `@throws` tags add these statuses to the [OpenAPI documentation](/docs/api/rest-bundle/documentation).
89 +
90 + Notes:
91 +
92 + * An exception with code `0` becomes an internal RPC error and returns `500`.
93 + * RPC core validation errors (`#[RPC\Assertions]`, parameter types) return `400` with `detail-data`. `422` applies only to a `ConstraintsImposedException` that reaches the REST layer directly.
94 + * An unknown method or path gives `404`, but a wrong HTTP method for an existing path gives `405`.