Map HATEOAS links to actual API links - spring-boot

I'm trying to implement a HATEOAS Rest Client using Spring Boot.
Right now, I'm stuck in a point where I need to convert HATEOAS into an actual API URI.
If I post a new object of type Customer like:
{
"name": "Frank",
"address": "http://localhost:8080/address/23"
}
And then I retrieved with a request to http://localhost:8080/api/customer/1`, HATEOAS gives me something like
{
"name": Frank,
"_links": {
"address": {
"href": "http://localhost:8080/api/customer/1/address"
}
}
}
Is it possible to convert a link of the form of http://localhost:8080/api/customer/1/address to an API call like http://localhost:8080/api/address/23 ?

If you see what HATEOS returns after you say,
GET: http://localhost:8080/api/customer/1
is
{
"name": Frank,
"_links": {
"address": {
"href": "http://localhost:8080/api/customer/1/address"
}
}
}
According to Understanding HATEOS,
It's possible to build more complex relationships. With HATEOAS, the output makes it
easy to glean how to interact with the service without looking up a specification or
other external document
which means,
after you have received resource details with
http://localhost:8080/api/customer/1
what other operations are possible with the received resource those will be shown for easier/click thru access to your service/application,
here in this case HATEOS could find a link http://localhost:8080/api/customer/1/address that was accessible once you have customer/1 and from there if you want then without going anywhere else customer/1 's address could be found with /customer/1/address.
Similarly if you have /customer/1's occupation details then there would be another link below address link called http://localhost:8080/api/customer/1/occupation.
So if address is dependent on customer i.e. there can be no address without customer then your API endpoint has to be /api/customer/1/address and not directly /api/address/23.
However, after understanding these standards and logic behind HATEOS's such responses if you still want to go with your own links that may not align with HATEOS's logic you can use,
Link Object provided by LinkBuilder interface of HATEOS.
Example:
With object of type Customer like:
Customer c = new Customer( /*parameters*/ );
Link link= linkTo(AnyController.class).slash("address").slash(addressId);
customer.add(link);
//considering you want to add link `http://localhost:8080/api/address/23` and `23 is your addressID`.
Also you can create a list of Links and keep adding many such links to that list and then add that list to your object.
Hope this helps you !

Related

Post data to custom AL field in Business Central

I am trying to create an API call to add data into a custom field that was created on the Sales Order page using AL Extensions. The issue is when I try to do the api call through postman, I am getting "The Property "propertyName" does not exist on type 'Microsoft.NAV.salesOrder'". First of all, I don't even know if the API allows for this, so is it even possible? And secondly, if it is possible, is there a certain way to set up the API call or the Field through the AL Extension?
tableextension 50100 "AddProjectIdToSalesOrder" extends "Sales Header"
{
fields
{
field(50100; "CrmProjectId"; Guid)
{
Caption = 'Crm Project Id';
DataClassification = OrganizationIdentifiableInformation;
}
}
}
pageextension 50100 "AddProjectIdToSalesOrder" extends "Sales Order"
{
layout
{
addlast(General)
{
field("CRM Project Id"; Rec.CrmProjectId)
{
ApplicationArea = all;
ToolTip = 'The Guid of the related Project Record in the CRM environment';
}
}
}
}
This is how I am setting up the field with the AL extension, and for the post call, I am just creating a new Sales Order with a post and the body looks like:
{
"customerNumber" : "10000",
"CrmProjectId" : "random-guid"
}
And the error is "Bad Request": "The property 'CrmProjectId' does not exist on type 'Microsoft.NAV.salesOrder'. Make sure to only use property names that are defined by the type." Any help would be appreciated.
The Sales Order API is a separate page. It is not equivalent to the Sales Order page so you have to modify the API to accomplish what you want.
However the standard API's provided by Microsoft can't be extended.
You are left with two options:
Make a copy of the standard Sales Order API (this involves making a copy of all the linked APIs as well e.g. the Sales Line API).
Create a new API page with the single purpose of updating your new field. Then you would use the standard Sales Order API to create the Sales Order and then update CrmProjectId with a second call to your custom API page.

Is it ok to use an array of objects in Rest Controller

I'm writing a controller that handles an array of object, something like:
#PostMapping("/post")
public void saveEmployeeArray(#RequestBody Emoloyee[] employeeArray)
{
// Method body
}
Keep in mind that, in this case, employees are usually created, update, or deleted in bulk, meaning, I usually need to handle multiple employees at the same time.
A colleague told me that although it works I should only handle one employee in the controller as, according to him, it's rest best practice. But he didn't present an alternative to the issue of having to handle multiple employees most of the time, other than making multiple requests.
My question is, how is the best way to handle multiple objects?
If your list of Employee is inside another data structure (e.g. company), you can offer PATCH operation for outer resource to add or change a list of Employees. JSON PATCH is specified in RFC 6902, see https://datatracker.ietf.org/doc/html/rfc6902/
The JSON body of a PATCH request would look like this:
[
{ "op": "add", "path": "/employees", "value": { "name": "Employee 1", ...} },
{ "op": "add", "path": "/employees", "value": { "name": "Employee 2", ...} },
...
]
The body is a list of PATCH operations to change the addressed data structure at several places within one request, so it fits to your requirement.
The specification in RFC 6902 offers more than just adding elements, you can also remove, replace, move, copy and test. Have a look in the RFC for good and simple examples.

(Ember.js) How to save Sideloaded data from ajax call?

Good day!
Just want to ask on how to create a JSONAPISerializer for an ajax call?
From what I understand on the docs. I must first create a model before I can make a JSONAPISerializer. But I need to call a custom endpoint that is not listed as my model.
My goal is to pushPayload all the sideloaded data coming from my endpoint. but the problem is :
{
"data":[
{
"type":"promotions-verify", <----- it's not an actual model
"attributes":{
"cart-items-discount-in-cents":21900
},
"relationships":{...}, <---- sideloaded data that I want to push on ember-data
}],
"included": [] <---- sideloaded data that I want to push on ember-data
}
Is there a reason you can't make a model for promotions-verify? That would be the cleanest way to implement loading in the side-loaded data, since Ember would handle much of the serialization/pushing to the store for you.
If that isn't possible and you make an ajax request, you may need to map the relationships and included payloads to match up with one another (Lodash _.map() could work well for this). Then you can manually push that data (pushPayload) to the Ember store, while ensuring that the items you're pushing also have models and serializers.
Also, I'm not sure if this was accidental, but your example payload doesn't conform to JSON API standards – the relationships object should be nested within data. This will affect how Ember serializes the data, as it's expecting:
{
"data": [{
"id": 1,
"type": "promotions-verify",
"attributes": {},
"relationships": {}
}],
"included": []
}

How to share location with public tag with Glympse REST API

I am trying to share a location to a public tag in Glympse with their REST API. My application is creating a ticket with
/v2/users/self/create_ticket
After that I am trying to add that ticket to the public tag/group (which is already available).
/v2/tickets/${ticketid}/append_data
{
[
"t": ${timestamp},
"pid": 0,
"n": "card_id",
"v": card_id // I am putting the group ID here
]
}
After that I have no clue how to proceed. I find the Glympse API description very confusing, so I tried several API functions but none of them worked, like updating the card
/v2/cards/${groupdid}/ticket
error: { result: 'failure', response: {}, meta: { error: 'invalid_access',
error_detail: 'Error processing request',
time: 1506944558077 } }
or simply just adding location data (no error but nothing visible in the tag)
/v2/tickets/${ticketid}/append_location.
Can you help me with the flow how to share the data to a public tag? I cannot find anything like that for the REST API. I could find this link: https://developer.glympse.com/docs/core/client-sdk/guides/common/groups but it does not help me with JavaScript and REST.
Thank you
Cards are only for private groups. To share to a public group (public tag) after you create the ticket, you create an invite of type "group" where the address is the name of the tag.
https://developer.glympse.com/docs/core/api/reference/tickets/id/create_invite/post
The group type appears to be missing from that reference page, but that is the type to use.
For example, to share to a tag named "testtag123":
tickets/[ticket_id]/create_invite?locale=en_US&region=en_US&type=group&send=server&address=testtag123&name=testtag123

HATEOAS with multiple pages

Is there a rule to define a sequential list of links using HATEOAS?
It is easy to add self, next and previous links to a HATEOAS-based response. Below is a sample response:
{
links : [{
rel : "next",
href : "http://localhost:8080/persons?page=1&size=20"
}],
content : [{
id: "",
name: "",
lastname: "",
age: 0
}],
pageMetadata : {
size : 20,
totalElements : 30,
totalPages : 2,
number : 0
}
};
So, pagination with next and previous links is not difficult but I couldn't figure out how it is possible to access for example 10th page directly, for example using a select element (let's assume there are more than 10 pages). Should I add all the links to the response as, for example, page1, page2, page3, etc? Of course I know the format of the request so I could just create the appropriate query but that seems kind of wrong because the whole point seems to be not relying on the actual link of the service. I am not an expert on this issue and I couldn't find an answer in this site or from Google.
Thanks in advance.
The common solution is to use URI templates. Specifically, section 3.2.8 regarding Form-Style Query Expansion.
Assuming both page and size are optional, you would construct your link URI template as localhost:8080/persons{?page,size}.
URI templates do not define which values are acceptable; there's no mechanism for restricting page or size to numeric values. As such, you should include this template in addition to your existing next/prev links. I'm not exactly sure which link relation best describes such a resource, but "collection" doesn't seem too far off.
In order to use URI templates, you'll need a library in whichever languages will be producing/consuming the template. A quick search produced a list of libraries in various languages which you may find helpful.
Lastly, depending on your hypermedia format you may need to specify a templated URI as such. For example, HAL+JSON uses a boolean templated property.
{
"_links": {
"collection": {
"href": "/persons{?page,size}",
"templated": true
}
}
}

Resources