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
Employee
Employee

(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!

148 REPLIES 148

seVladimirs
Galactic 5
Galactic 5

4aad13e1746f9d43e995d2d0bbf1185d2bea565ea487d4d8eb72ccd3986684f3

What a bummer I got the value containing only one word, no JS this time for me šŸŒž

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

Here's an extra sub-task for you, then, @seVladimirs ... 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 šŸ‘ šŸ™‚

  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 šŸ˜‰

šŸ‘Œ

ajmaradiaga
Employee
Employee

a051e43183593a227412695eeac769af9977f07e5efb4bd11c40bd52d1a04396

kumarniti4
Employee
Employee
0 Kudos

9ded87220fcf379da1b79b71b1f4b92fb34eb6013ba17afd98be56e5a2111181

prachetas
Galactic 2
Galactic 2
0 Kudos

1663ab8519dc065f9daf05f3f223c8317eb916685d1133a792bbd3edee1c7578

ceedee666
Galactic 3
Galactic 3
0 Kudos
cee733d6f01eead517dec302cd5b83bf2b852a47e4a07abf31efe6f4b1947fdb

 Corrected my sollution.

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

 

0 Kudos

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
Galactic 4
Galactic 4
0 Kudos

62ba810562db9972a928b5a817437f80f62b12c759f7953efdfdbdf61b3aca33

eakucuk
Galactic 3
Galactic 3
0 Kudos

00e93c286b4316a2fb58bfbb8ddd274703e8059a39e21314eb373b0340d9330e

se38
Galactic 4
Galactic 4
0 Kudos

fe09f888ade21a2b8e21ba691918cd1d074e99b3b2cbfbf58bc851e1bdaabf3b

Had to Google my product, looks tasty.

huseyindereli
Galactic 2
Galactic 2
0 Kudos

d0cd8095643dde270642afc54c8821748e4c9672aa550c5d38b4542874a99567

choujiacheng
Galactic 3
Galactic 3
0 Kudos

15e994e0614b367768215e40e4b204b37dd2779ffdc6933bf910508265cb58b1

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.

0 Kudos

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

Ella
Galactic 2
Galactic 2
0 Kudos

8beb73847eab0a613c8a5b21f15f49976d1e06a52e33e027350f5c8855513c5c

nicoschoenteich
Employee
Employee
0 Kudos

4684f9e47770606d1de40da018df043fd82403ecb48fca2fbf3b6f29f9b263ef

cguttikonda24
Galactic 3
Galactic 3
0 Kudos

a1138824bdfd28c4502b72b77fd7b66a54a906dae28a1bff087e8292e2dec671

harsh_itaverma
Galactic 4
Galactic 4
0 Kudos

79219553d1d4b3db034cf454fd3b979f0483f87804b2cd90fa144ab817449b67

GRABLERE
Employee
Employee
0 Kudos

37824e4b854dcc5d374912d4689254c8a9790a30661d6cb7419f59eb6fb18a75

abdullahgunes
Galactic 3
Galactic 3
0 Kudos
f20f018fc9c1398d25b0f7d70f7efde82f87eddc6fef2863ffb9e548c2965db0

nex
Galactic 3
Galactic 3
0 Kudos

f6c611acdbdc239ff3fb0d721a10883845e260eaee6e3f3c75c3659acd18db51

dan_wroblewski
Employee
Employee
0 Kudos

ae0f9ded9991563e76048fb48ce8bd91a5d923bd31b8629689ffba16cb9563dc




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

Tomas_Buryanek
Galactic 4
Galactic 4
0 Kudos

1bf10bba633032c070cb4acacbc0c3d44333399c2b9afd351fb0d297ebb0739d

Frank_Haschick
Galactic 3
Galactic 3
0 Kudos

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 ;-))

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

 

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 šŸ˜

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

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 šŸ™‚ 

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 šŸ™‚

Hey @Frank_Haschick @seVladimirs @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!

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

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

amazing, so my wild guess was right šŸ™ƒ