Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
Arley
Product and Topic Expert
Product and Topic Expert

coauthor: ralf.handl

SAP Fiori Elements Object Page Floorplan Draft Scenario Displaying an Error Message


 

OData Error Message



Introduction


When building applications with the SAP Cloud Application Programming Model (CAP), generic service providers significantly shorten the service development time by providing many out-of-the-box solutions to recurring tasks allowing you to focus on the specific business logic of your application, thus reducing the overall implementation effort.

Examples of out-of-the-box solutions to recurring tasks — but not limited to — are the following:

  • Serving of create, read, update, and delete (CRUD) requests of exposed entities
  • Generic handlers implementations, including standard input validation

These capabilities allow you to develop fully-fledged running services quickly by composing Core Data Services and running simple command-line interface (CLI) commands.

However, things don’t always go straightforward when processing an OData request. HTTP-based applications, such as those using SAP Fiori Elements, send OData requests to backend systems. Assuming that a request payload is invalid or partially invalid, for example, a user might have entered a value in a field that violates a database constraint, but in another field, the user might have entered a valid value. Thus, the valid input can be stored successfully in the backend system, but the invalid input cannot be stored, and it is essential that the user is informed about this error.

This blog post will give you a brief overview of the messaging system for transferring error, warning, and information messages from OData V4 services to HTTP client applications such as those using SAP Fiori Elements.

 

Possible Outcomes when Processing an OData Request


 

In the context of HTTP clients and HTTP servers based on CAP — or similar server-side technologies — things don’t always go straightforward when processing an OData request. There are three possible outcomes:

  1. The request is processed successfully. Period.
  2. The server couldn’t process the request at all, and we want to inform the user what went wrong and why
  3. The request is processed successfully, but there were some side effects or things that went not relatively straightforward and are so essential that we have to inform the user via SAP Fiori Messaging.

OData defines how to handle the first and second scenarios, but the standard does not mandate how to handle the third scenario. Now focusing on the third scenario, for example, if in an SAP Fiori Elements Object Page Floorplan draft scenario, a user doesn't fill all mandatory text input fields and tries to activate/save the draft to store the active version of a business entity in the back-end system. In that scenario, errors will occur that prevent the entity from being stored, and the user must be able to see possible error messages on the screen.

 

Error Response Body in OData


Block Diagram of Messaging System for Transferring an Error Messages from an OData V4 Services to an SAP Fiori Elements Based Application


The HTTP response body's content adheres to the standard OData specification for an error response body in addition to an SAP-specific format. So errors, warnings, and info messages are transferred to HTTP clients in a format that reflects the most common requirements for messages targeted at users.

The OData HTTP error response body is a JSON object with a single name/value pair named error that contains the following name/value pairs:

  • A machine-readable error code — a language-independent string
  • A human-readable, language-dependent message representation of the error summarizing the problem
  • An optional target — a relative resource path to correlate the error message
  • An array of details, each with a code, message, and target
  • An optional innererror structured instance with service-defined content

Additionally, for extensibility reasons,

implementations can add custom annotations of the form @namespace.termname or property@namespace.termname to any JSON object, where property MAY or MAY NOT match the name of a name/value pair within the JSON object.

In CAP, the error response body is extended using the @Common.numericSeverity instance annotation to add a severity to the message.

Values for the @Common.numericSeverity instance annotation

Severity Numeric severity Description

Success1Success — no action required
Info2Information — no action required
Warning3Warning — action may be required
Error4Error — action is required

 

Common Format for Error Target

The target of an error/warning response is always a relative resource path segment that is appended to the path part of the request URL (for GET, PATCH, PUT, and DELETE requests) or the Location response header (for POST requests that create a new entity), resulting in an OData request URL that is used to retrieve the target of the error message.

For GET, PATCH, PUT, and DELETE requests to a single entity or complex-type instance, the target is:

  • Empty — if the error is related to the addressed resource as a whole —, for example, a Sales Order, or
  • A property path relative to the addressed resource, that is to say, if a forward slash followed by the value of the target is appended to the path part of the request URL, the result is an OData request URL identifying the target of the error message
  • For POST requests that create a new entity, the target is relative to the Location response header identifying the newly created resource. Otherwise, it follows the rules for GET, PATCH, PUT, and DELETE requests to a single entity.Note: this includes the creation of dependent entities via a multi-valued navigation property, for example, POST Orders(42)/Items. The target is still relative to the newly created entity, in this case, an order item. Messages targeting the containing entity, in this case, order 42, can only be returned if there's a to-1 navigation property back from the item to the order.
  • For all request types, the target may start with a forward slash. In this case, the target is interpreted as an OData path relative to the service root URL.

Examples


Given the following CAP based model snippet:


entity Headers {
key ID : UUID;
text : String;
items : Composition of many Items on items.header = $self;
}

entity Items {
key ID : UUID;
header : Association to one Headers @assert.target;
text : String @mandatory;
}

 

Patching a mandatory field with null — assume that the item with the ID 7be6d296-9e7a-3505-b72e-4c7b98783578 exists in the database


 

HTTP Request

 


PATCH http://localhost:4004/service-name/Items(ID=7be6d296-9e7a-3505-b72e-4c7b98783578) HTTP/1.1
Accept: application/json;odata.metadata=minimal
Prefer: return=minimal
Content-Type: application/json;charset=UTF-8

{
"text": null
}


 

HTTP Response

 


HTTP/1.1 400 Bad Request
OData-Version: 4.0
content-type: application/json;odata.metadata=minimal
Connection: close
Content-Length: 98

{
"error": {
"code": "400",
"message": "Value is required",
"target": "text",
"@Common.numericSeverity": 4
}
}


 

Stdout (log message)

 


[cds] - PATCH /service-name/Items(ID=7be6d296-9e7a-3505-b72e-4c7b98783578)
[cds] - Error: Value is required {
code: 'ASSERT_NOT_NULL',
target: 'text',
args: [ 'text' ],
entity: 'serviceName.Items',
element: 'text',
type: 'cds.String',
value: null,
numericSeverity: 4,
id: '1090822',
level: 'ERROR',
timestamp: 1653756442547
}

 

Creating an item that references a non-existing header — @assert.target Constraint


 

HTTP Request — assume that a header with the ID "796e274a-c3de-4584-9de2-3ffd7d42d646" doesn't exist in the database

 


POST http://localhost:4004/service-name/Items HTTP/1.1
Accept: application/json;odata.metadata=minimal
Prefer: return=minimal
Content-Type: application/json;charset=UTF-8

{
"ID": "86b07ae1-2c9b-4a29-953c-b257f5a737f4",
"text": "lorem cillum",
"header_ID": "796e274a-c3de-4584-9de2-3ffd7d42d646"
}

 

HTTP Response

 


HTTP/1.1 400 Bad Request
OData-Version: 4.0
content-type: application/json;odata.metadata=minimal
Connection: close
Content-Length: 105

{
"error": {
"code": "400",
"message": "Value doesn't exist",
"target": "header_ID",
"@Common.numericSeverity": 4
}
}

 

Stdout (log message)

 


[cds] - POST /service-name/Items
[cds] - Error: Value doesn't exist {
code: 'ASSERT_TARGET',
target: 'header_ID',
args: [ 'header_ID' ],
entity: 'serviceName.Items',
element: 'header_ID',
type: 'cds.UUID',
value: '796e274a-c3de-4584-9de2-3ffd7d42d646',
numericSeverity: 4,
id: '1090822',
level: 'ERROR',
timestamp: 1653756615316
}


Notice that in this case, the header managed to-one association is annotated with the @assert.target annotation to check whether the target entity referenced by the association (the reference's target) exists. As the foreign key header_ID input does not have a corresponding primary key in the associated/referenced target entity/table, the OData service respond with an HTTP error message.

 

Multiple Errors


 

HTTP Request

 


POST http://localhost:4004/service-name/Items HTTP/1.1
Accept: application/json;odata.metadata=minimal
Prefer: return=minimal
Content-Type: application/json;charset=UTF-8

{
"header_ID": "796e274a-c3de-4584-9de2-3ffd7d42d646"
}

 

HTTP Response

 


HTTP/1.1 400 Bad Request
OData-Version: 4.0
content-type: application/json;odata.metadata=minimal
Connection: close
Content-Length: 304

{
"error": {
"code": "400",
"message": "Multiple errors occurred. Please see the details for more information.",
"details": [
{
"code": "400",
"message": "Value is required",
"target": "text",
"@Common.numericSeverity": 4
},
{
"code": "400",
"message": "Value doesn't exist",
"target": "header_ID",
"@Common.numericSeverity": 4
}
]
}
}

 

Stdout (log message)

 


[cds] - POST /service-name/Items
[cds] - Error: Multiple errors occurred. Please see the details for more information. {
details: [
{
code: 'ASSERT_NOT_NULL',
message: 'Value is required',
target: 'text',
args: [ 'text' ],
entity: 'serviceName.Items',
element: 'text',
type: 'cds.String',
value: undefined,
numericSeverity: 4
},
{
code: 'ASSERT_TARGET',
message: "Value doesn't exist",
target: 'header_ID',
args: [ 'header_ID' ],
entity: 'serviceName.Items',
element: 'header_ID',
type: 'cds.UUID',
value: '796e274a-c3de-4584-9de2-3ffd7d42d646',
numericSeverity: 4
}
],
id: '1090822',
level: 'ERROR',
timestamp: 1653756684026
}


 

Deep Update


 

HTTP Request

 

POST http://localhost:4004/service-name/Headers HTTP/1.1
Accept: application/json;odata.metadata=minimal
Prefer: return=minimal
Content-Type: application/json;charset=UTF-8

{
"ID": "9910905a-b331-419b-a202-7c73588a6637",
"text": "cupidatat anim"
}

PATCH http://localhost:4004/service-name/Headers(ID=9910905a-b331-419b-a202-7c73588a6637) HTTP/1.1
Accept: application/json;odata.metadata=minimal
Prefer: return=minimal
Content-Type: application/json;charset=UTF-8

{
"text": "aliqua sint",
"items": [{
"ID": "f509356d-2e1a-4501-a9fe-5435a46b4531",
"header_ID": "9910905a-b331-419b-a202-7c73588a6637",
"text": null
}]
}

 

HTTP Response

 

HTTP/1.1 400 Bad Request
OData-Version: 4.0
content-type: application/json;odata.metadata=minimal
Connection: close
Content-Length: 145

{
"error": {
"code": "400",
"message": "Value is required",
"target": "items(ID=f509356d-2e1a-4501-a9fe-5435a46b4531)/text",
"@Common.numericSeverity": 4
}
}

 

Stdout (log message)

 

[cds] - POST /service-name/Headers 
[cds] - PATCH /service-name/Headers(ID=9910905a-b331-419b-a202-7c73588a6637)
[cds] - Error: Value is required {
code: 'ASSERT_NOT_NULL',
target: 'items(ID=f509356d-2e1a-4501-a9fe-5435a46b4531)/text',
args: [ 'text' ],
entity: 'serviceName.Items',
element: 'text',
type: 'cds.String',
value: null,
numericSeverity: 4,
id: '1090822',
level: 'ERROR',
timestamp: 1653757487211
}

 

3 Comments
sandeepmalhotra
Participant
0 Kudos
Thanks Arley for such nice explanation

Kindly let me know if there any way to frame custom error messages in the handler ( before, on and after events)

In case of multiple errors , how to pass error messages in req.Error function

 

Thanks once again
Arley
Product and Topic Expert
Product and Topic Expert
0 Kudos

You can find detailed information about handling errors in CAP in the official documentation at:

https://cap.cloud.sap/docs/node.js/events#req-error

Error messages are collected within the `req.errors` property, an array-like data structure.

Pass a custom error message to `req.error()`:

req.error({
code: 'Some-Custom-Code',
message: 'Some Custom Error Message',
target: 'some_field',
status: 418
})

 

arvind_patel3
Explorer
0 Kudos
Thanks Arley for summarising the targets in different scenario.

 

We have oData V4 in our application. We currently have a scenario where we try to create multiple entities using one batch call (one batch call contains multiple post requests). Now the success response for those may contain information message for all or some requests.

we populate Information message in "sap-message" which is available by default in Message Model. However to set the right target on the UI, how to relate these sap-message to the correct requests in UI.

 

below is the request and response.
--batch_id-1687510555509-674
Content-Type: multipart/mixed; boundary=changeset_f9835216-3792-42ed-ab17-4a155d7b41c8

--changeset_f9835216-3792-42ed-ab17-4a155d7b41c8
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 0.0

HTTP/1.1 201 Created
Location: XXXXXXX/CreateRecordForResource(0ba94790-a5a6-49a1-893a-91f4b6b26374)
OData-EntityID: XXXXXX/CreateRecordForResource(0ba94790-a5a6-49a1-893a-91f4b6b26374)
Content-Type: application/json;ieee754compatible=true;odata.metadata=minimal
OData-Version: 4.0
sap-messages: [{"code":"<none>","message":"The Records starts at 2023-03-01, which is outside the selected time period. Adjust the time period to display the entire assignment.","numericSeverity":2,"target":""}]
Content-Length: 6613

{"@odata.context":XXXXXContextParameters}
--changeset_f9835216-3792-42ed-ab17-4a155d7b41c8
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 1.0

HTTP/1.1 201 Created
Location: XXXXXXX/CreateRecordForResource(efc706a2-bbae-4fae-9057-6105991a742a)
OData-EntityID: XXXXXX/CreateRecordForResource(efc706a2-bbae-4fae-9057-6105991a742a)
Content-Type: application/json;ieee754compatible=true;odata.metadata=minimal
OData-Version: 4.0
sap-messages: [{"code":"<none>","message":"The Records starts at 2023-03-01, which is outside the selected time period. Adjust the time period to display the entire assignment.","numericSeverity":2,"target":""}]
Content-Length: 6613

{"@odata.context":XXXXXContextParameters}
--changeset_f9835216-3792-42ed-ab17-4a155d7b41c8
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 2.0

HTTP/1.1 201 Created
Location: XXXXXXX/CreateRecordForResource(03982741-4b11-4ed4-ac10-92619576550d)
OData-EntityID: XXXXXXX/CreateRecordForResource(03982741-4b11-4ed4-ac10-92619576550d)
Content-Type: application/json;ieee754compatible=true;odata.metadata=minimal
OData-Version: 4.0
sap-messages: [{"code":"<none>","message":"The Records starts at 2023-03-01, which is outside the selected time period. Adjust the time period to display the entire assignment.","numericSeverity":2,"target":""}]
Content-Length: 6613

{"@odata.context":XXXXXContextParameters}
--changeset_f9835216-3792-42ed-ab17-4a155d7b41c8--
--batch_id-1687510555509-674--

--batch_id-1687510555509-674
Content-Type: multipart/mixed;boundary=changeset_id-1687510555509-675

--changeset_id-1687510555509-675
Content-Type:application/http
Content-Transfer-Encoding:binary
Content-ID:0.0

POST CreateRecordForResource HTTP/1.1
Accept:application/json;odata.metadata=minimal;IEEE754Compatible=true
Accept-Language:en
X-CSRF-Token:0ab8f4e6be7f5513-en7JaZeKa-bubkFhldMR269BQ1o
Content-Type:application/json;charset=UTF-8;IEEE754Compatible=true

{"resourceRequest_ID":"37a287e7-8151-46d9-b14b-8bc3a4de9c3b","resource_ID":"fb1a0939-1261-46c9-a68f-424b85ace9a0","requestStartDate":"9999-04-01","requestEndDate":"9999-09-01","projectRoleName":"ProjectRole"}
--changeset_id-1687510555509-675
Content-Type:application/http
Content-Transfer-Encoding:binary
Content-ID:1.0

POST CreateRecordForResource HTTP/1.1
Accept:application/json;odata.metadata=minimal;IEEE754Compatible=true
Accept-Language:en
X-CSRF-Token:0ab8f4e6be7f5513-en7JaZeKa-bubkFhldMR269BQ1o
Content-Type:application/json;charset=UTF-8;IEEE754Compatible=true

{"resourceRequest_ID":"65eea683-8bff-4aaf-bc8c-b2e7ee264e52","resource_ID":"fb1a0939-1261-46c9-a68f-424b85ace9a0", "projectRoleName":"ProjectRole"}
--changeset_id-1687510555509-675
Content-Type:application/http
Content-Transfer-Encoding:binary
Content-ID:2.0

POST CreateRecordForResource HTTP/1.1
Accept:application/json;odata.metadata=minimal;IEEE754Compatible=true
Accept-Language:en
X-CSRF-Token:0ab8f4e6be7f5513-en7JaZeKa-bubkFhldMR269BQ1o
Content-Type:application/json;charset=UTF-8;IEEE754Compatible=true

{"resourceRequest_ID":"62fa7a7e-451a-4682-860d-96e619f63451","resource_ID":"fb1a0939-1261-46c9-a68f-424b85ace9a0","projectRoleName":"ProjectRole"}
--changeset_id-1687510555509-675--
--batch_id-1687510555509-674--
Group ID: $auto

 

One way is to populate the target with new create guid on the UI but as I understand, it is now allowed to pass Records GUID as target for this given scenario. Request your help on this.

 

Thanks

Arvind