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: 

SAP Developer Challenge - APIs - Task 3 - Have a Northbreeze product selected for you

qmacro
Developer Advocate
Developer Advocate
19,308

(Check out the SAP Developer Challenge - APIs blog post for everything you need to know about the challenge to which this task relates!)

In this task you'll learn a bit about actions and functions in OData V4.

Background

While OData V2 did have function imports, the advent of OData V4 brought about enhancements in this area and we now have a clean distinction between different types of orthogonal endpoints that can be presented in an OData service alongside the entity sets, singletons and more. See the Actions and functions sections of the OData V4 and SAP Cloud Application Programming Model talk slides for more info.

With CAP's support for OData V4's actions and functions ready for us to enjoy, it's a good opportunity to try things out here.

In addition to the entity sets we saw in the previous task, the Northbreeze OData V4 service also sports an action. If you check the service's metadata document document you'll see the evidence of this action.

First, it appears alongside the EntitySet elements within the EntityContainer element:

<EntityContainer Name="EntityContainer">
  <EntitySet Name="Products" EntityType="Northbreeze.Products">
    <NavigationPropertyBinding Path="Category" Target="Categories"/>
    <NavigationPropertyBinding Path="Supplier" Target="Suppliers"/>
  </EntitySet>
  <EntitySet Name="Suppliers" EntityType="Northbreeze.Suppliers">
    <NavigationPropertyBinding Path="Products" Target="Products"/>
  </EntitySet>
  <EntitySet Name="Categories" EntityType="Northbreeze.Categories">
    <NavigationPropertyBinding Path="Products" Target="Products"/>
  </EntitySet>
  <EntitySet Name="Summary_of_Sales_by_Years" EntityType="Northbreeze.Summary_of_Sales_by_Years"/>
  <EntitySet Name="TotalProducts" EntityType="Northbreeze.TotalProducts"/>
  <ActionImport Name="selectProduct" Action="Northbreeze.selectProduct"/>
</EntityContainer>

The action itself is described in more detail along with the EntityType elements. Here it is in full:

<Action Name="selectProduct" IsBound="false">
  <Parameter Name="communityid" Type="Edm.String"/>
  <ReturnType Type="Edm.String"/>
</Action>

You can see from this detail that:

  • it's an unbound action
  • it expects a single parameter communityid
  • it returns a result

Basically, this action expects to receive your SAP Community ID as input, and, based on that ID, will choose a product for you.

Actions vs functions: One of the key differences between actions and functions in OData V4 is that while functions may not have side effects, actions may well have side effects. This means that normally functions will be used for read-only operations, while actions can be used for operations that make some sort of change. But they don't have to. And here, we're using an action, rather than a function, mostly because actions require the POST HTTP method (because they may have side effects), rather than GET. And requiring you to use HTTP POST for this task makes things more interesting.

Your task

Your task is to call this unbound action, supplying your SAP Community ID for the communityid parameter. The response you will receive is a JSON representation, containing a value property, the value for which is the product that has been selected for you (based on your SAP Community ID).

This is the value you must hash and then post that hash as a new reply to this discussion thread, as described in Task 0 - Learn to share your task results.

Here's an example. Calling the action with qmacro as the value for communityid causes this to be returned in response (pretty printed for readability):

{
  "@odata.context": "$metadata#Edm.String",
  "value": "Rössle Sauerkraut"
}

The product name Rössle Sauerkraut is what needs to be hashed. Note, in this case, that any value passed in the URL path must be properly URL encoded. So while the value test, for example, needs no specific URL encoding, this value must be URL encoded, in order to be successfully transported in the URL path and accurately received and parsed by the hash service. In other words, the value:

Rössle Sauerkraut

needs to be URL encoded so it looks like this:

R%C3%B6ssle%20Sauerkraut

When inserted into the URL path, it will therefore look like this:

/v1/hash(value='R%C3%B6ssle%20Sauerkraut')

Hints and tips

You may want to peruse the content of the talk OData V4 and SAP Cloud Application Programming Model, in particular the short Actions and functions section.

You may also wish to watch the replay of the Hands-on SAP Dev live stream episode Back to basics: OData - the Open Data Protocol - Part 5 - Actions & functions.

You can find out what URL encoding is all about in the Wikipedia entry for URL encoding. You're likely to find either a built-in or a library function in your favorite language to do that, for example JavaScript has encodeURIComponent and Python has a quote function in urllib.parse.

Finally, you may wish to read this short post OData query operations and URL encoding the system query options with curl, written in part as a response to a great comment on the previous task.

For discussion

What's the difference between the meanings of "bound" and "unbound" in this context? What does the fact that the selectProduct action is "unbound" suggest to us?

Remember, if you're posting your thoughts on these discussion questions, remember to do it in a reply separate from your hash reply!

149 REPLIES 149

vladimirs_semikins
Active Contributor
10,248

4aad13e1746f9d43e995d2d0bbf1185d2bea565ea487d4d8eb72ccd3986684f3

10,245

What a bummer I got the value containing only one word, no JS this time for me 🌞

10,223

Haha, sorry about that 🙂 The algorithm for picking a product for a given community member is quite simple, and as it's fixed (and consistent, hopefully!) a given community member will always get the same product.

0 Kudos
10,227

Here's an extra sub-task for you, then, @vladimirs_semikins ... as you were pretty quick, you could help out on this thread and give hints to any of those who need them as to how to call an OData V4 action with an HTTP POST request. Not too many hints, just enough to give them an idea 👍 🙂

10,134
  1. Create a correct URL to properly access the `/selectProduct` action, you can find an example at https://github.com/qmacro/odata-v4-and-cap/blob/main/slides.md#action-examples-post.
  2. Next, use any REST client (curl, postman, insomania, etc.)
  3. Make a POST request with a JSON body type, including the number of attribute(s) defined in the metadata - see EntityType elements inside <Action Name="selectProduct" IsBound="false"> ... </Action>
  4. Encode if required using JS as mentioned
  5. Create Hash as defined in Task 0
  6. Post message here 😉

9,946

👌

ajmaradiaga
Developer Advocate
Developer Advocate
10,241

a051e43183593a227412695eeac769af9977f07e5efb4bd11c40bd52d1a04396

kumarniti4
Product and Topic Expert
Product and Topic Expert
0 Kudos
10,205

9ded87220fcf379da1b79b71b1f4b92fb34eb6013ba17afd98be56e5a2111181

prachetas
Participant
0 Kudos
10,203

1663ab8519dc065f9daf05f3f223c8317eb916685d1133a792bbd3edee1c7578

ceedee666
Active Contributor
0 Kudos
10,206
cee733d6f01eead517dec302cd5b83bf2b852a47e4a07abf31efe6f4b1947fdb

 Corrected my sollution.

ceedee666
Active Contributor
10,202

Had to look up how to send JSON data with the request library and how to perform URL encoding. Two nice little learnings for a Wednesday morning. Thanks @qmacro! 👏

https://github.com/ceedee666/sap-dev-challenge-apis-in-python

 

qmacro
Developer Advocate
Developer Advocate
0 Kudos
9,965

That makes me happy, thanks for sharing. I tried to find the balance between being easy but also requiring some thought or looking something up 👍 And thanks for sharing your Python Notebook!

SandipAgarwalla
Active Contributor
0 Kudos
10,168

62ba810562db9972a928b5a817437f80f62b12c759f7953efdfdbdf61b3aca33

eakucuk
Explorer
0 Kudos
10,128

00e93c286b4316a2fb58bfbb8ddd274703e8059a39e21314eb373b0340d9330e

UweFetzer_se38
Active Contributor
0 Kudos
10,129

fe09f888ade21a2b8e21ba691918cd1d074e99b3b2cbfbf58bc851e1bdaabf3b

10,125

Had to Google my product, looks tasty.

huseyindereli
Active Contributor
0 Kudos
10,125

d0cd8095643dde270642afc54c8821748e4c9672aa550c5d38b4542874a99567

choujiacheng
Explorer
0 Kudos
10,127

15e994e0614b367768215e40e4b204b37dd2779ffdc6933bf910508265cb58b1

10,122

A bound action is attached to an entity type and can be overloaded. An unbound action is not bounded to an entity and cannot be overloaded. As selectProducts is an unbound action, the URL does not need to call the entity Products, for example, to receive the answer, but rather be called on its own.

qmacro
Developer Advocate
Developer Advocate
0 Kudos
10,004

Nice one, that's a good description of the distinction!

Ella
Explorer
0 Kudos
10,125

8beb73847eab0a613c8a5b21f15f49976d1e06a52e33e027350f5c8855513c5c

nicoschoenteich
Developer Advocate
Developer Advocate
0 Kudos
10,118

4684f9e47770606d1de40da018df043fd82403ecb48fca2fbf3b6f29f9b263ef

cguttikonda24
Participant
0 Kudos
10,094

a1138824bdfd28c4502b72b77fd7b66a54a906dae28a1bff087e8292e2dec671

harsh_itaverma
Participant
0 Kudos
10,087

79219553d1d4b3db034cf454fd3b979f0483f87804b2cd90fa144ab817449b67

GRABLERE
Product and Topic Expert
Product and Topic Expert
0 Kudos
10,065

37824e4b854dcc5d374912d4689254c8a9790a30661d6cb7419f59eb6fb18a75

abdullahgunes
Participant
0 Kudos
10,038
f20f018fc9c1398d25b0f7d70f7efde82f87eddc6fef2863ffb9e548c2965db0

nex
Explorer
0 Kudos
10,041

f6c611acdbdc239ff3fb0d721a10883845e260eaee6e3f3c75c3659acd18db51

Dan_Wroblewski
Developer Advocate
Developer Advocate
0 Kudos
9,966

ae0f9ded9991563e76048fb48ce8bd91a5d923bd31b8629689ffba16cb9563dc




--------------
See all my blogs and connect with me on Twitter / LinkedIn

Tomas_Buryanek
Active Contributor
0 Kudos
9,951

1bf10bba633032c070cb4acacbc0c3d44333399c2b9afd351fb0d297ebb0739d

-- Tomas --

Frank_Haschick
Explorer
0 Kudos
9,970

I can't generate a hash for my product because the hash service always returns

{
    "error": {
        "code""404",
        "message""Invalid resource path \"challenge.hash(value='...')\""
    }
}
 
I escaped my product, but somehow I guess it does not like the %27 ... any ideas on how to resolve that?
If I use seVladimirs name, it works perfectly (but he has no need to escape, so it's easy ;-))

9,962

I've tried with your community id and it works just fine. Not sure why you have %27 as it would represent symbol ' see https://www.w3schools.com/tags/ref_urlencode.ASP 

I would assume a majority of ProductNames should have %20 which stands for "space".

btw if you are using Postman you can skip encoding and simple do a GET to /hash endpoint just ensure that settings "Encode URL automatically" is enabled.

seVladimirs_0-1691592380651.png

 

9,946

Thank's for trying!

You are right, I had a space in my communityId. But if you would use "Frank Haschick" (so space instead of underline), you get "Uncle Bob's Organic Dried xxx" as product. And this name really contains a '. And with this name, you can't generate a hash (at least I wasn't able to).

Thank you very much for checking! Love to do pair-programming 😁

9,714

Yes, you are right 🙂 Indeed there is a symbol ' in the product name, which returns an error. Maybe @qmacro can help us here 🙂

9,729

I have the same challenge with my product, always got 404 response from the hash service.
I managed to get a hash result when I just removed the ' character from the parameter, so I didn't replace it with the %27. 

Maybe the hash is wrong, but at least I got something 🙂 

9,685

I found the following in CAP chanegelog Changelog 2020 | CAPire (cloud.sap):

  • [cds@4.2.2] cds deploy --to sqlite has aligned its escaping rules for parsing CSV data with SAP HANA's hdbtabledata. A " character can be escaped by another " as before, but only if contained in a quoted string, that means, "A""B" leads to A"B, while A""B stays A""B, and "" results in an empty string.


So I've took the idea to escape ' with another ' and it worked 

e.g. ProductName = 'hello' awesome people' => encodeURIComponent("hello'' awesome people") => hello''%20awesome%20people

then do a GET request

seVladimirs_0-1691655262450.png

 @qmacro just needs to confirm if the implementation of the `action` is receiving the correct string.

but anyways, it's seems like a bug in CAP 🙂

9,628

Hey @Frank_Haschick @vladimirs_semikins @shotokka @Naguco et al.

Interesting issue! But what's awesome to see is the conversation around it here. Thanks for digging in, folks. I'm on vacation today (back tomorrow) but for now, I wanted to send a brief reply here, with a suggestion.

If you "escape" the single quote(s) by doubling them up, and before URL encoding the resulting string (for inserting into the value='...' part), then things should be OK. 

Here's an example, based on one of your community ID specific products (revealing some inner mechanics which I may share at the end of the challenge 🙂  

# developer-challenge-apis (main *%=)
; ./tasks/3-*/solve shotokka
Gustaf's Knäckebröd
# developer-challenge-apis (main *%=)
; ./tasks/3-*/solve shotokka | sed "s/'/''/g"
Gustaf''s Knäckebröd
# developer-challenge-apis (main *%=)
; ./tasks/3-*/solve shotokka | sed "s/'/''/g" | ./urlenc
Gustaf%27%27s%20Kn%C3%A4ckebr%C3%B6d 

Making a call to the hash service with this value will work. Here's that value used, from above:

; curl --verbose --header 'communityid: shotokka' --url "localhost:4004/v1/hash(value='Gustaf%27%27s%20Kn%C3%A4ckebr%C3%B6d')"
> GET /v1/hash(value='Gustaf%27%27s%20Kn%C3%A4ckebr%C3%B6d') HTTP/1.1
> Host: localhost:4004
> User-Agent: curl/7.74.0
> Accept: */*
> communityid: shotokka
> 
< HTTP/1.1 200 OK
< X-Powered-By: Express
< Content-Type: text/plain; charset=utf-8
< Content-Length: 64
< Date: Thu, 10 Aug 2023 10:24:12 GMT
< 
83359110b4f21e9849afeeda2bb58a5f340bfead3d22e94ea1fc500a5c0f39a1

Hope that helps!

9,600

Thanks for the clarification! This really opened up what's going on.

qmacro
Developer Advocate
Developer Advocate
9,579

I meant to also add a link to the relevant section of the OData V4 spec on this matter. Here it is:

http://docs.oasis-open.org/odata/odata/v4.01/cs01/part2-url-conventions/odata-v4.01-cs01-part2-url-c...

screenshot 2023-08-10 at 13.18.56.png

9,572

amazing, so my wild guess was right 🙃