|
1 | 1 | from __future__ import annotations |
2 | 2 |
|
| 3 | +import logging |
3 | 4 | from collections import defaultdict |
4 | 5 | from typing import ( |
5 | 6 | TYPE_CHECKING, |
|
11 | 12 | Union, |
12 | 13 | ) |
13 | 14 |
|
| 15 | +from fastapi import HTTPException, status |
| 16 | +from pydantic import ValidationError |
14 | 17 | from starlette.requests import Request |
15 | 18 |
|
16 | 19 | from fastapi_jsonapi import RoutersJSONAPI |
|
22 | 25 | if TYPE_CHECKING: |
23 | 26 | from fastapi_jsonapi.data_layers.base import BaseDataLayer |
24 | 27 |
|
| 28 | +log = logging.getLogger(__name__) |
25 | 29 | AtomicResponseDict = TypedDict("AtomicResponseDict", {"atomic:results": List[Any]}) |
26 | 30 |
|
27 | 31 |
|
@@ -84,12 +88,35 @@ async def handle(self) -> Union[AtomicResponseDict, AtomicResultResponse, None]: |
84 | 88 | local_ids_cache: LocalIdsType = defaultdict(dict) |
85 | 89 | success = True |
86 | 90 | previous_dl: Optional[BaseDataLayer] = None |
87 | | - for operation in prepared_operations: |
| 91 | + for idx, operation in enumerate(prepared_operations, start=1): |
88 | 92 | dl: BaseDataLayer = await operation.get_data_layer() |
89 | 93 | await dl.atomic_start(previous_dl=previous_dl) |
90 | 94 | previous_dl = dl |
91 | | - operation.update_relationships_with_lid(local_ids=local_ids_cache) |
92 | | - response = await operation.handle(dl=dl) |
| 95 | + try: |
| 96 | + operation.update_relationships_with_lid(local_ids=local_ids_cache) |
| 97 | + response = await operation.handle(dl=dl) |
| 98 | + except (ValidationError, ValueError) as ex: |
| 99 | + log.exception( |
| 100 | + "Validation error on atomic action ref=%s, data=%s", |
| 101 | + operation.ref, |
| 102 | + operation.data, |
| 103 | + ) |
| 104 | + errors_details = { |
| 105 | + "message": f"Validation error on operation #{idx}", |
| 106 | + "ref": operation.ref, |
| 107 | + "data": operation.data.dict(), |
| 108 | + } |
| 109 | + if isinstance(ex, ValidationError): |
| 110 | + errors_details.update(errors=ex.errors()) |
| 111 | + elif isinstance(ex, ValueError): |
| 112 | + errors_details.update(error=str(ex)) |
| 113 | + else: |
| 114 | + raise |
| 115 | + # TODO: json:api exception |
| 116 | + raise HTTPException( |
| 117 | + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, |
| 118 | + detail=errors_details, |
| 119 | + ) |
93 | 120 | # response.data.id |
94 | 121 | if not response: |
95 | 122 | # https://jsonapi.org/ext/atomic/#result-objects |
|
0 commit comments