Application Development Discussions
Join the discussions or start your own on all things application development, including tools and APIs, programming models, and keeping your skills sharp.
cancel
Showing results for 
Search instead for 
Did you mean: 

Task 6 - API endpoint with payload required (July Developer Challenge - "Reverse APIs")

qmacro
Developer Advocate
Developer Advocate
4,137

This is a task in the July Developer Challenge - "Reverse APIs".

This task gets you to add another API endpoint to the plain "REST" service you have, but will be a little different in its definition, implementation, and how it's called.

Background

HTTP-based API endpoints, whatever the protocol, can differ in various ways, such as:

  • how they're called (which HTTP method)
  • whether they expect a payload (a body) in the HTTP request
  • what they do, what effect they have at the server side
  • what resource and representation they return (if any)

HTTP is an application protocol and there are already well understood semantic meanings for each of the methods (also known as "verbs", where the URLs are the "nouns"). There are some protocols that abuse or ignore these semantics, particularly the set of heavyweight "Web Services" from a decade or so ago, known as "WS-Deathstar" (due to their weight and complexity), SOAP, and even today we have GraphQL that arguably abuses HTTP as a mere transport layer. Yes, "abuse" is a strong word, but, as my bio hints at, it's an opinion I hold, and is one of many 🙂

bio.png

Anyway, in addition to those differences listed earlier, one must also consider the duumvirate (yes, I did just use that word) of:

  • idempotency
  • side effects

Idempotency (from the Latin, as is, of course, duumvirate) essentially means "having the same effect". To quote the excellent MDN docs on Idempotency:

An HTTP method is idempotent if the intended effect on the server of making a single request is the same as the effect of making several identical requests.

Examples of HTTP methods that have this idempotency characteristic are: GET, HEAD, PUT and DELETE (yes!).

A side effect in this context means that in handling or otherwise fulfilling an HTTP request, state is changed on the server. HTTP methods that do not have side effects are known as safe. Examples of HTTP methods that do have side effects, i.e. that are not safe, are POST, PUT and DELETE. Examples of HTTP methods that are side effect free, i.e. safe, are GET, HEAD and OPTIONS.

Semantic meanings are important, as they form part of the "contract" of behaviour between clients and servers.

Why am I telling you all this? Because CDL, CAP's definition language that you use to define your overall CDS model, has a couple of keywords that are important in this context.

Actions and functions

In Capire's section on Providing Services, there's a subsection on Actions & Functions that explain more. I recommend you go and read that section, then come back here.

Did you notice the explanation was in the context of OData? That's because it's where the specific definitions originate, but these definitions make sense even outside the context of OData, such as when serving via the "REST" protocol.

In summary, we can see that both actions and functions are for providing API endpoints that represent resources beyond the standard CRUD+Q requests, although:

  • Functions are for defining API endpoints that are safe, these endpoints are to be requested with HTTP GET, and any data should be supplied in the URL. There's a related rule here that functions must be addressed (in the URL) with brackets, even if there's no data to supply. Functions, on the whole, are for returning data.
  • Actions are for defining API endpoints that may have side effects, and these endpoints must be requested with HTTP POST; any data that is to be supplied must be as a payload to the POST request. In addition, actions may return nothing (just an HTTP status code and headers).

In addition (but not relevant for this task), both functions and actions can be bound or unbound. The idea of "unbound" is what we generally think of in terms of simple API endpoints. The "bound" concept is from OData, and is about calling an action or function relative to a specific entity. In such cases there's an extra implicit "binding parameter" that the implementation receives in the request, as a connection to the instance of the entity to which the called function is bound.

The first task in the context of this current plain "REST" service was an unbound function (note the brackets, despite no parameters):

/rest/plain/theAnswer()
This task will require you to define and implement a simple unbound action, which means you must:
  • use the action keyword
  • call it using a request with the HTTP POST method
  • send the data in the payload of the request

You will also not need to use brackets at the end of the URL.

The requirements

Here are the specific requirements for this task.

In the plain service you have already, define an unbound action that expects a list of integers. The implementation of that unbound action must determine the highest of those numbers, and return it as a single integer.

Note that if you are running CAP Node.js version 8 then you will have to annotate your action definition with @open. See the Open Types section of the blog post Automatic validation in OData and REST calls with CAP for more information.

Submitting to the TESTER

Now you're ready to submit your CANDIDATE service, with this new API endpoint, to the TESTER!

Note that the TESTER will be calling your action via HTTP POST, and supplying the list of integers as an array, in a JSON representation, in other words, like this:

POST /rest/plain/highestValue HTTP/1.1
Host: localhost:8000
Content-Type: application/json
Content-Length: 19

[54, 203, -3, 0, 1]

The payload

The task identifier you need to supply in the payload of your submission is: plain-highestValue.

You'll have already done this sort of thing previously so just head back there for the more detailed instructions if you need them, or to the the section titled "The Tester service, and making a test request" in the main challenge blog post.

You'll need to submit a JSON payload like this:

{
  "communityid": "<your-community-id>",
  "serviceurl": "<the-URL-of-your-service>",
  "task": "plain-highestValue"
}
And, just as with the previous (and all further tasks):
  • the value for the communityid property should be your ID on this SAP Community platform (e.g. mine is "qmacro")

  • the value for the serviceurl property should be the absolute URL (i.e. including the scheme), of your CANDIDATE service which contains the API endpoint (see ℹ️ A note on URLs and services).

That's it!

Logging of test results

Remember that you can check on your progress, and the progress of your fellow participants - all requests are logged and are available in an entity set served by the TESTER service. The entity set URL is https://developer-challenge-2024-07.cfapps.eu10.hana.ondemand.com/tester/Testlog and being an OData V4 entity set, all the normal OData system query options are available to you for digging into that information.

Until the next task, have fun, and if you have any questions or comments, leave them below!

32 REPLIES 32

mwn
Participant
4,045

That was great! I now know how to handle arrays, especially when passing through cURL. The ES6 spread syntax was a nice find using Google. 

mwn_0-1721376934663.png

 

qmacro
Developer Advocate
Developer Advocate
0 Kudos
3,970

Great to hear, well done! 

tobiasz_h
Active Participant
3,959

Hello,
Another cool challenge!

tobiasz_h_0-1721400335251.png

 

spassaro
Participant
3,911

Done with a reduce method! btw:

Why did the array go to therapy?

Because it had too many issues to reduce!

spassaro_0-1721406641310.png

 

qmacro
Developer Advocate
Developer Advocate
0 Kudos
3,238

Hehe.

BTW I was hoping someone would use `reduce` ... it's one of my fav functions 🙂

M-K
Active Participant
3,864

Here's my submission (finally it has worked):

MK_0-1721415384688.png

Don't try to sort an array with "toSorted", it works well in BAS but gives you a "500 Internal Server Error" after deploying 😕

 

Alpesa1990
Participant
3,814

My submission for task 6.

Alpesa1990_0-1721428392527.png

Good challenge!

 

qmacro
Developer Advocate
Developer Advocate
0 Kudos
3,236

Thanks!

sudarshan_b
Participant
3,802

What a task this was!! Took me some debugging to get the sort working, I enjoyed every bit of it. 

Here's my submission - 

sudarshan_b_0-1721429706535.png

Thank you.

 

qmacro
Developer Advocate
Developer Advocate
0 Kudos
3,234

That's great to hear!

gphadnis2000
Participant
3,621

Here is my submission for Task no 6.

Another challenging task and lot more to learn.Lot of debugging required as well.

gphadnis2000_0-1721469028369.png

Another challenging task and lot more things to learn.

gphadnis2000_0-1721470461412.png

 

 

 

qmacro
Developer Advocate
Developer Advocate
0 Kudos
3,234

That makes me happy. Thanks!

sachin_sap
Explorer
3,442

Would it not be more idiomatic for CAP to use a curl request with some input like below:

 

{ "input" = [54,203,-3,0,1] }

 

 this would allow the service to type check "input" is an array of numbers with service definition like 

 

action maxValue(input: array of Int32) returns Int32

 

the current service definition - at least my implementation will throw a HTTP 500 error if the request payload has an array element which is not a number 

example: below request payload may throw an error with highestValue service 

 

[54,203,-3,0,1,"i am not a number"]

 

qmacro
Developer Advocate
Developer Advocate
0 Kudos
3,218

Great thoughts! I'm not sure what is more idiomatic here, a simple array (`[1,2,3]`) or an array as the value of a property in an object (`{"vals": [1,2,3]}`). Can you share the exact error message(s) that are thrown when you supply a string in the array (like you showed)? And good work for digging in and finding out!

3,118

Error with(`[1,2,3,"not a number"]`)

this maybe because I used Math.max() function

 

HTTP/1.1 500 Internal Server Error

{
  "error": {
    "code": "500",
    "message": "Failed to validate return value of type 'cds.Int32' for custom action 'plain.highestValue': Value NaN is invalid."
  }
}

 

 

error with (`{"input": [1,2,3,4,5,"not a number"]}`)

 

 

HTTP/1.1 400 Bad Request
{
  "error": {
    "message": "Value not a number is not a valid Int32",
    "target": "input[5]",
    "code": "400",
    "@Common.numericSeverity": 4
  }
}

 

 

MatLakaemper
Participant
3,136

Here my submission, top Challenge, i have learned a lot,

Regards, Matthias

MatLakaemper_0-1721567349992.png

MatLakaemper_1-1721567378265.png

 

 

 

MioYasutake
Active Contributor
2,888

@qmacro 

Could you please tell me how to define an action that takes a plain array as input?

I can define an array with a name like this:

action highestValue(input: array of Int32) returns Int32;

But I cannot define it without a name like this:

action highestValue(array of Int32) returns Int32;

 

qmacro
Developer Advocate
Developer Advocate
2,318

Hey there @MioYasutake - this is a very good question; just wanted you to know that I'm not ignoring it, but answering it may take me some time as I have some background explanation I want to share about it 😉

qmacro
Developer Advocate
Developer Advocate
0 Kudos
2,003

Hi there - I wrote this up - let me know what you think! "Automatic validation in OData and REST calls with CAP" https://qmacro.org/blog/posts/2024/07/24/automatic-validation-in-odata-and-rest-calls-with-cap/ 

vineelaallamnen
Explorer
2,815

Task 6 

vineelaallamnen_0-1721610936047.png

 

mxmw
Explorer
2,652

Took some time troubleshooting but Passed 🙂

mxmw_0-1721641293342.png

 

Thanks for the fun challenges!

YogSSohanee
Participant
2,600

Hello @qmacro ,

My submission for task 6: 

YogSSohanee_0-1721647889867.png

API call:

YogSSohanee_1-1721647925691.png

 

 

geek
Participant
2,550

Took so many wrong turns trying to get to this:

geek_0-1721667826548.pnggeek_1-1721668006098.png

Was tempted to use reduce but google sent me to Math.max... Really challenging. Thank you.

qmacro
Developer Advocate
Developer Advocate
0 Kudos
2,402

Excellent! Would it help to have a quick discussion on different ways in JS to do this? There are many, I could suggest a few ...

 

qmacro
Developer Advocate
Developer Advocate
0 Kudos
2,070

BTW, talking of Math.max, I wrote a short blog post on different ways to achieve what's required here in the implementation. You (and others, perhaps) may find it interesting: Highest value in JS - different ways.

Liyon_SV
Explorer
2,381

Passed 😀

Liyon_SV_2-1721709329593.png

 

 

 

Ruthiel
Product and Topic Expert
Product and Topic Expert
2,257

I'm really confused with that one!

Locally it seams to be working:

Ruthiel_0-1721746266539.png

But it always fails with the deployed app:

Ruthiel_1-1721746361145.png

 

qmacro
Developer Advocate
Developer Advocate
0 Kudos
2,227

What do the logs reveal? (cf logs [--recent] <your-cf-app-name>)

MioYasutake
Active Contributor
1,978

My submission for task 6. Finally, I got it working.

MioYasutake_0-1721852538017.png

I was using CAP version 8, and that's why passing a plain array did not work (it got rejected). Thanks @qmacro, for the explanations in your blog post.

I noticed that if I didn't define any parameter for the action, data passed at runtime was still ignored. However, if I defined a dummy parameter as shown below, the data was passed to the event handler.

@open action highestValue(dummy: Int32) returns Int32;

 

qmacro
Developer Advocate
Developer Advocate
0 Kudos
1,923

Great stuff, thanks @MioYasutake. And good insight into the no vs dummy parameter aspect!

sandeepGottipati
Explorer
1,677

Screenshot 2024-07-26 at 7.46.46 PM.png

Here is my submission for the task-6.

ManojKumarVarma
Explorer
1,222

Hello,

Done Task 6,

ManojKumarVarma_0-1722247020320.png


Thanks,

Manoj Kumar Potharaju.