Skip to content

Commit 685a926

Browse files
Improve Error Handling target API (#2252)
Prefer showing targets via Messages API, as this allows collecting multiple error messages and should be preferred in applications. Also reduced usage of the String-based API, as this has limitations in draft messages scenarios. --------- Co-authored-by: René Jeglinsky <[email protected]>
1 parent 63ecd4d commit 685a926

File tree

1 file changed

+32
-68
lines changed

1 file changed

+32
-68
lines changed

java/event-handlers/indicating-errors.md

Lines changed: 32 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,7 @@ To know which error codes and messages are available by default, you can have a
149149
## Target
150150

151151
When SAP Fiori interprets messages it can handle an additional `target` property, which, for example, specifies which element of an entity the message refers to. SAP Fiori can use this information to display the message along the corresponding field on the UI.
152-
When specifying messages in the `sap-messages` HTTP header, SAP Fiori mostly ignores the `target` value.
153-
Therefore, specifying the `target` can only correctly be used when throwing a `ServiceException` as SAP Fiori correctly handles the `target` property in OData V4 error responses.
152+
SAP Fiori interprets `target` property in OData V4 error messages and [draft state messages](../../advanced/fiori#validating-drafts). You can specify the `target` when throwing a `ServiceException` or when setting a validation error using the [Messages API](#messages).
154153

155154
A message target is always relative to an input parameter in the event context.
156155
For CRUD-based events this is always the `cqn` parameter, which represents and carries the payload of the request.
@@ -162,7 +161,7 @@ By default a message target always refers to the CQN statement of the event. In
162161
As CRUD event handlers are often called from within bound actions or functions (e.g. `draftActivate`), CAP's OData adapter adds a parameter prefix to a message target referring to the `cqn` parameter only when required.
163162

164163
::: info
165-
When using the `target(String)` API, which specifices the full target as a `String`, no additional parameter prefixes are added by CAP's OData adapter. The `target` value is used as specified.
164+
When using the `target(String)` API, which specifies the full target as a `String`, no additional parameter prefixes are added by CAP's OData adapter. When using this API, [draft state messages](../../advanced/fiori#validating-drafts) can't be invalidated automatically on `PATCH`.
166165
:::
167166

168167
Let's illustrate this with the following example:
@@ -208,63 +207,39 @@ Here, we have a `CatalogService` that exposes et al. the `Books` entity and a `B
208207

209208
### CRUD Events
210209

211-
Within a `Before` handler that triggers on inserts of new books a message target can only refer to the `cqn` parameter:
210+
Within a `Before` handler that triggers on inserts of new books, a message target can only refer to the `cqn` parameter. You can use generic `String`-based APIs or a typed API backed by the interfaces generated from the CDS model:
212211

213-
``` java
212+
```java
214213
@Before
215214
public void validateTitle(CdsCreateEventContext context, Books book) {
216-
// ...
215+
Messages messages = context.getMessages();
217216

218217
// event context contains the "cqn" key
218+
// target implicitly refers to cqn
219+
messages.error("No title specified").target(b -> b.get("title"));
219220

220-
// implicitly referring to cqn
221-
throw new ServiceException(ErrorStatuses.BAD_REQUEST, "No title specified")
222-
.messageTarget(b -> b.get("title"));
223-
224-
// which is equivalent to explicitly referring to cqn
225-
throw new ServiceException(ErrorStatuses.BAD_REQUEST, "No title specified")
226-
.messageTarget("cqn", b -> b.get("title"));
221+
// which is equivalent to a target explicitly referring to cqn
222+
messages.error("No title specified").target("cqn", b -> b.get("title"));
227223

228-
// which is the same as using plain string
229-
// assuming direct POST request
230-
throw new ServiceException(ErrorStatuses.BAD_REQUEST, "No title specified")
231-
.messageTarget("title");
232-
233-
// which is the same as using plain string
234-
// assuming surrounding bound action request with binding parameter "in",
235-
// e.g. draftActivate
236-
throw new ServiceException(ErrorStatuses.BAD_REQUEST, "No title specified")
237-
.messageTarget("in/title");
224+
// using interfaces generated from the CDS model
225+
messages.error("No title specified").target(Books_.class, b -> b.title());
238226
}
239227
```
240228

241-
Instead of using the generic API for creating the relative message target path, CAP Java SDK also provides a typed API backed by the CDS model:
242-
243-
``` java
244-
@Before
245-
public void validateTitle(CdsCreateEventContext context, Books book) {
246-
// ...
247-
248-
// implicitly referring to cqn
249-
throw new ServiceException(ErrorStatuses.BAD_REQUEST, "No title specified")
250-
.messageTarget(Books_.class, b -> b.title());
251-
}
252-
```
229+
Depending on the OData request context, the resulting target in this example is either `title` (assuming a `POST` or `PATCH` request) or `in/title` (assuming a bound action like `draftActivate`).
253230

254-
This also works for nested paths with associations:
231+
You can also specify targets that point to elements within associations:
255232

256-
``` java
233+
```java
257234
@Before
258235
public void validateAuthorName(CdsCreateEventContext context, Books book) {
259-
// ...
236+
Messages messages = context.getMessages();
260237

261-
// using un-typed API
262-
throw new ServiceException(ErrorStatuses.BAD_REQUEST, "No title specified")
263-
.messageTarget(b -> b.to("author").get("name"));
238+
// using untyped API
239+
messages.error("No title specified").target(b -> b.to("author").get("name"));
264240

265241
// using typed API
266-
throw new ServiceException(ErrorStatuses.BAD_REQUEST, "No author name specified")
267-
.messageTarget(Books_.class, b -> b.author().name());
242+
messages.error("No title specified").target(Books_.class, b -> b.author().name());
268243
}
269244
```
270245

@@ -276,30 +251,25 @@ The same applies to message targets that refer to an action or function input pa
276251
``` java
277252
@Before
278253
public void validateReview(BooksAddReviewContext context) {
279-
// ...
280-
254+
Messages messages = context.getMessages();
281255
// event context contains the keys "reviewer", "rating", "title", "text",
282256
// which are the input parameters of the action "addReview"
283257

284258
// referring to action parameter "reviewer", targeting "firstName"
285-
throw new ServiceException(ErrorStatuses.BAD_REQUEST, "Invalid reviewer first name")
286-
.messageTarget("reviewer", r -> r.get("firstName"));
259+
messages.error("Invalid reviewer first name").target("reviewer", r -> r.get("firstName"));
287260

288261
// which is equivalent to using the typed API
289-
throw new ServiceException(ErrorStatuses.BAD_REQUEST, "Invalid reviewer first name")
290-
.messageTarget(BooksAddReviewContext.REVIEWER, Reviewer_.class, r -> r.firstName());
262+
messages.error("Invalid reviewer first name")
263+
.target(BooksAddReviewContext.REVIEWER, Reviewer_.class, r -> r.firstName());
291264

292265
// targeting "rating"
293-
throw new ServiceException(ErrorStatuses.BAD_REQUEST, "Invalid review rating")
294-
.messageTarget("rating");
266+
messages.error("Invalid review rating").target("rating");
295267

296268
// targeting "title"
297-
throw new ServiceException(ErrorStatuses.BAD_REQUEST, "Invalid review title")
298-
.messageTarget("title");
269+
messages.error("Invalid review title").target("title");
299270

300271
// targeting "text"
301-
throw new ServiceException(ErrorStatuses.BAD_REQUEST, "Invalid review text")
302-
.messageTarget("text");
272+
messages.error("Invalid review text").target("text");
303273
}
304274
```
305275

@@ -310,27 +280,21 @@ For the `addReview` action that is the `Books` entity, as in the following examp
310280
``` java
311281
@Before
312282
public void validateReview(BooksAddReviewContext context) {
313-
// ...
314-
283+
Messages messages = context.getMessages();
315284
// referring to the bound entity `Books`
316-
throw new ServiceException(ErrorStatuses.BAD_REQUEST, "Invalid book description")
317-
.messageTarget(b -> b.get("descr"));
318-
319-
// or (using the typed API, referring to "cqn" implicitly)
320-
throw new ServiceException(ErrorStatuses.BAD_REQUEST, "Invalid book description")
321-
.messageTarget(Books_.class, b -> b.descr());
285+
messages.error("Invalid book description").target(b -> b.get("descr"));
322286

323-
// which is the same as using plain string
324-
throw new ServiceException(ErrorStatuses.BAD_REQUEST, "Invalid book description")
325-
.messageTarget("in/descr");
287+
// or using the typed API
288+
messages.error("Invalid book description").target(Books_.class, b -> b.descr());
326289
}
327290
```
328291

292+
In the case of OData the resulting target is `in/descr`, assuming the default name `in` is used for the binding parameter name.
293+
329294
::: tip
330-
The previous examples showcase the target creation with the `ServiceException` API, but the same can be done with the `Message` API and the respective `target(...)` methods.
295+
The previous examples showcase the target creation with the `Messages` API, but the same can be done with the `ServiceException` API and the respective `messageTarget(...)` methods.
331296
:::
332297

333-
334298
## Error Handler { #errorhandler}
335299

336300
An [exception](#exceptions) thrown in an event handler will stop the processing of the request. As part of that, protocol adapters trigger the `ERROR_RESPONSE` event of the [Application Lifecycle Service](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/application/ApplicationLifecycleService.html). By default, this event combines the thrown exception and the [messages](#messages) from the `RequestContext` in a list to produce the error response. OData V4 and V2 protocol adapters will use this list to create an OData error response with the first entry being the main error and the remaining entries in the details section.

0 commit comments

Comments
 (0)