2023 Aug 09 6:21 AM - edited 2023 Aug 11 3:21 PM
(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.
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:
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 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')
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.
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!
2023 Aug 09 6:53 AM
2023 Aug 09 6:56 AM
2023 Aug 09 7:02 AM
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.
2023 Aug 09 7:03 AM
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 👍 🙂
2023 Aug 09 8:42 AM
2023 Aug 09 1:27 PM
2023 Aug 09 6:59 AM
2023 Aug 09 7:09 AM
2023 Aug 09 7:21 AM
2023 Aug 09 7:21 AM - edited 2023 Sep 04 10:20 AM
2023 Aug 09 7:26 AM
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
2023 Aug 09 1:26 PM
2023 Aug 09 8:23 AM
2023 Aug 09 9:09 AM
2023 Aug 09 9:13 AM
2023 Aug 09 9:16 AM
2023 Aug 09 9:27 AM
2023 Aug 09 9:28 AM
2023 Aug 09 9:48 AM
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.
2023 Aug 09 1:14 PM
2023 Aug 09 9:51 AM
2023 Aug 09 10:01 AM
2023 Aug 09 10:40 AM
2023 Aug 09 11:08 AM
2023 Aug 09 11:40 AM
2023 Aug 09 12:28 PM - edited 2023 Aug 09 12:30 PM
2023 Aug 09 12:31 PM
2023 Aug 09 2:30 PM
2023 Aug 09 3:22 PM
2023 Aug 09 3:33 PM
I can't generate a hash for my product because the hash service always returns
2023 Aug 09 3:47 PM
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.
2023 Aug 09 4:02 PM
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 😁
2023 Aug 10 7:09 AM
2023 Aug 10 7:15 AM
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 🙂
2023 Aug 10 9:06 AM - edited 2023 Aug 10 9:15 AM
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
@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 🙂
2023 Aug 10 11:28 AM
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!
2023 Aug 10 12:16 PM
2023 Aug 10 1:20 PM
I meant to also add a link to the relevant section of the OData V4 spec on this matter. Here it is:
2023 Aug 10 1:27 PM - edited 2023 Aug 10 1:28 PM