OData Cheat Sheet (CAP)

Aus MattWiki

This article holds notes on how to quickly get certain responses from a OData service. Hotever, it is specifically based on a SAP Cloud Application Programming Model (CAP) OData service.

Sources for OData in general: https://www.odata.org/getting-started/basic-tutorial/


OData V4

Querying Data

GET serviceRoot/People?$count=true

Returns the number of records retreived

Accessing OData Services from Command Line

For further details on OData Services see OData Cheat Sheet (CAP)

Examples for OData V4 Get Requests via URL

The following table shows examples of what can be requested with a given url.

What URL
Bookshop service root http://localhost:4004/odata/v4/bookshop
Bookshop service, all Books entities http://localhost:4004/odata/v4/bookshop/Books
Bookshop service, Books entity with specified key ID in parentheses http://localhost:4004/odata/v4/bookshop/Books(3b1e1d9e-9563-4cd3-a448-69b8b8f1c384)http://localhost:4004/odata/v4/bookshop/Books/3b1e1d9e-9563-4cd3-a448-69b8b8f1c384
Bookshop service, Books entity with filter on title field http://localhost:4004/odata/v4/bookshop/Books?$filter=contains(title,%27The%20Player%20Of%20Games%27)
Bookshop service, value from field title from Books entity with key ID in parentheses http://localhost:4004/odata/v4/bookshop/Books(3b1e1d9e-9563-4cd3-a448-69b8b8f1c384)/title
Unbound totalStock function from Bookshop service http://localhost:4004/odata/v4/bookshop/totalStock()
Bound stockValue function from Books entity in Bookshop service http://localhost:4004/odata/v4/bookshop/Books/3b1e1d9e-9563-4cd3-a448-69b8b8f1c384/stockValue()

Reading from Services

Read output of services via curl and pipe it through jq for nicer formatting:

curl -s 'localhost:4004/odata/v4/bookshop/Orders' | jq

Output (example for Bookshop repo):

{
  "@odata.context": "$metadata#Orders",
  "value": [
    {
      "ID": "6091d4ab-650e-4afa-90bb-163305e473a2",
      "comment": "second order"
    },
    {
      "ID": "ac5aeb9f-c7cd-4f52-ab4a-9c0313ded402",
      "comment": "first order"
    }
  ]
}

Reading Unbound Functions from Services

Read output of a unbound function via curl and pipe it through jq for nicer formatting:

curl -s 'localhost:4004/odata/v4/bookshop/totalStock()' | jq

Output:

{
  "@odata.context": "$metadata#Edm.Int32",
  "value": 147
}

Reading Record with Key from Service

Read output of one particular record from a service via curl and pipe it through jq for nicer formatting:

curl -s 'localhost:4004/odata/v4/bookshop/Orders/6091d4ab-650e-4afa-90bb-163305e473a2' | jq
curl -s 'localhost:4004/odata/v4/bookshop/Orders(6091d4ab-650e-4afa-90bb-163305e473a2) | jq

Output (example for Bookshop repo):

{
  "@odata.context": "$metadata#Orders/$entity",
  "ID": "6091d4ab-650e-4afa-90bb-163305e473a2",
  "comment": "second order"
}

Reading Bound Functions from Service

Read output of a function bound to a service entity while providing a key ID:

curl -s 'localhost:4004/odata/v4/bookshop/Books/3b1e1d9e-9563-4cd3-a448-69b8b8f1c384/stockValue()' | jq

Output:

{
  "@odata.context": "../$metadata#Edm.Int32",
  "value": 42
}

Reading from Services while returning Request Header

curl -is 'localhost:4004/odata/v4/bookshop/Orders'

Output:

HTTP/1.1 200 OK
X-Powered-By: Express
X-Correlation-ID: c8dbb1e1-6124-473f-b482-4efe091d1f5b
OData-Version: 4.0
content-type: application/json;odata.metadata=minimal
Date: Sat, 22 Jun 2024 14:57:24 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Content-Length: 491

{"@odata.context":"$metadata#Books","value":[{"ID":"3b1e1d9e-9563-4cd3-a448-69b8b8f1c384","title":"The Player Of Games","author_ID":"d5adee57-ef4a-441e-bfa7-9acac6e647e4","stock":5},{"ID":"dc3c659f-29c5-4b21-b3b0-44c58bae5306","title":"The Hitch Hiker's Guide To The Galaxy","author_ID":"01afafdf-0b4a-475b-b107-77fd3c9157da","stock":42},{"ID":"f584c9c9-c076-423a-9638-4c5917a4cbac","title":"Mostly Harmless","author_ID":"01afafdf-0b4a-475b-b107-77fd3c9157da","stock":100,"urgency":"HIGH"}]}

Continuous Reading from Services after File Change

This section is not specitic to CAP, but in design time it perhaps helps to reload for example the read services from the section before after changing the source code.

ls srv/* | entr -c bash -c 'curl -s localhost:4004/odata/v4/bookshop/Orders | jq .'

entr is a tool which looks for file changes on a list of supplemended files and executes a command. So ls srv/* supplements a file list and pipes it to entr which in turn executes the bash command curl to query the OData service. The result here would be for example the following one after every change to a file in the srv subdirectory:

{
  "@odata.context": "$metadata#Orders",
  "value": [
    {
      "ID": "6091d4ab-650e-4afa-90bb-163305e473a2",
      "comment": "second order"
    },
    {
      "ID": "ac5aeb9f-c7cd-4f52-ab4a-9c0313ded402",
      "comment": "first order"
    }
  ]
}

Remarks: When saving a file in the srv subdirectory and when using cds watch is used this will trigger a restart of the CDS server. As this takes some time the entr command could trigger the curl command before the CDS server has fully restarted. A possible solution to solve this issue is described below in section Custom Application Logic Within CAP, sub section Custom Server.

Hint: Check out the more robust CAP listening plugin from Qmacro: https://github.com/qmacro/cap-listening-plugin

Deep Insert into Service with POST

For performing a deep insert we first need a at least two level deep structure.

The following neworder.json file with a two-level deep structure is given:

{
    "comment": "New Order",
    "Items": [
            {
                "pos": 1,
                "quantity": 10
            },
            {
                "pos": 2,
                "quantity": 20
            }
        ]
}

To perform the deep insert put the json file into the service by utilizing curl. The filename needs to be prefixed with a @. As we are connecting to a web service we also need to add a header and specify the content type, in this case application/json:

curl --verbose --data @neworder.json --header 'content-type: application/json' --url 'localhost:4004/odata/v4/bookshop/Orders'

By default curl with parameter --data uses the POST method. For more details on the HTTP methods see Notable RFCs Section HTTP Methods GET, POST, PUT, DELETE

We should receive an answer that is similar to the following one in that we are connected to localhost, and we posted to odata service and that the HTTP code was 201 Created:

*   Trying 127.0.0.1:4004...
* Connected to localhost (127.0.0.1) port 4004 (#0)
> POST /odata/v4/bookshop/Orders HTTP/1.1
> Host: localhost:4004
> User-Agent: curl/7.74.0
> Accept: */*
> content-type: application/json
> Content-Length: 193
> 
* upload completely sent off: 193 out of 193 bytes
* Mark bundle as not supporting multiuse
< HTTP/1.1 201 Created
< X-Powered-By: Express
< X-Correlation-ID: 4eee1d5a-5b43-4577-8257-a99b026a0c43
< OData-Version: 4.0
< content-type: application/json;odata.metadata=minimal
< Location: Orders(9416b371-2355-402a-b06d-34f5b2c61704)
< Date: Sat, 20 Apr 2024 12:23:52 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
< Content-Length: 280
< 
* Connection #0 to host localhost left intact
{"@odata.context":"$metadata#Orders(Items())/$entity","ID":"9416b371-2355-402a-b06d-34f5b2c61704","comment":"New Order","Items":[{"parent_ID":"9416b371-2355-402a-b06d-34f5b2c61704","pos":1,"quantity":10},{"parent_ID":"9416b371-2355-402a-b06d-34f5b2c61704","pos":2,"quantity":20}]}

Update Records via Service with PATCH

Following up the deep insert example above let's assume we want to update the Order with comment New Order. For this we create a neworder-update.json with a different content, i.e.:

{
  "comment": "New Order",
  "Items": [
          {
              "pos": 1,
              "quantity": 1000
          }
      ]
}

To perform the update we need to refer to the ID of the previously created Order to have this one updated. This happens by using the ID 9416b371-2355-402a-b06d-34f5b2c61704 from the server response above and adding it to the url after Order in parentheses:

curl -X PATCH --verbose --data @neworder-update.json --header 'content-type: application/json' --url 'localhost:4004/odata/v4/bookshop/Orders(9416b371-2355-402a-b06d-34f5b2c61704)' | jq .

The expected result is HTTP code 200 OK:

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0*   Trying 127.0.0.1:4004...
* Connected to localhost (127.0.0.1) port 4004 (#0)
> PATCH /odata/v4/bookshop/Orders(9416b371-2355-402a-b06d-34f5b2c61704) HTTP/1.1
> Host: localhost:4004
> User-Agent: curl/7.74.0
> Accept: */*
> content-type: application/json
> Content-Length: 121
> 
} [121 bytes data]
* upload completely sent off: 121 out of 121 bytes
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< X-Powered-By: Express
< X-Correlation-ID: 9a21bb8f-cfcc-4641-bc68-2f1bcbb7d5a8
< OData-Version: 4.0
< content-type: application/json;odata.metadata=minimal
< Date: Sat, 20 Apr 2024 12:34:13 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
< Content-Length: 198
< 
{ [198 bytes data]
100   319  100   198  100   121  19800  12100 --:--:-- --:--:-- --:--:-- 31900
* Connection #0 to host localhost left intact
{
  "@odata.context": "$metadata#Orders/$entity",
  "ID": "9416b371-2355-402a-b06d-34f5b2c61704",
  "comment": "New Order",
  "Items": [
    {
      "parent_ID": "9416b371-2355-402a-b06d-34f5b2c61704",
      "pos": 1,
      "quantity": 1000
    }
  ]
}

Check the results with queueing the service again:

curl -s 'localhost:4004/odata/v4/bookshop/Orders?$expand=Items' | jq
{
  "@odata.context": "$metadata#Orders(Items())",
  "value": [
    {
      ...
    },
    {
      "ID": "9416b371-2355-402a-b06d-34f5b2c61704",
      "comment": "New Order",
      "Items": [
        {
          "parent_ID": "9416b371-2355-402a-b06d-34f5b2c61704",
          "pos": 1,
          "quantity": 1000
        }
      ]
    },
    {
      ...
    }
  ]
}



OData Service Request Contents

Below are contents of request req from actions listed

Object Response Comment
req.target bookshop_Books returns req.target.name
req.params (1) ['3b1e1d9e-9563-4cd3-a448-69b8b8f1c384'] returns an array of length 1
req.params[0] '3b1e1d9e-9563-4cd3-a448-69b8b8f1c384' returns first element of the params array