Update an entire resource collection in a REST way - rest

I have a REST URI for a list of resources, something like:
http://foo.com/group/users
Each of these users has a sequence number and I want to expose a way to renumber those values for all the users in the collection and make this change available to everyone who accesses the list. Since this is an action on the collection as a whole, I'm not sure how to accomplish this.
I can envision a URL like http://foo.com/group/users?sequence=normalize but neither a PUT nor a POST really makes sense for the whole list, unless I submit the whole collection with the new numbers as the message data.
How can I make an update to an entire collection like this in a RESTful way without having to resend all the updated resources in the collection?

After the raffian's comment on my initial response, I reworked my answer to be more RESTful...
Use the method PATCH
This method is typically designed to update partially the state of a resource. In the case of a list resource, we could send a list with only the elements to update and the identifiers of elements in the list. The following request would be:
PATCH /group/users
[
{ "id": "userId1", "sequence": "newSequenceNumber1" },
{ "id": "userId2", "sequence": "newSequenceNumber2" },
(...)
]
Use the method POST on the list resource
This method is commonly used to add an element in the list managed by the resource. So if you want to leverage it for this action, we need to pass within the request an hint regarding the action to execute. We have the choice to add this either in a dedicated header or within the payload.
With the header approach, you will have something like that:
POST /group/users
X-Action: renumbering
[
{ "id": "userId1", "sequence": "newSequenceNumber1" },
{ "id": "userId2", "sequence": "newSequenceNumber2" },
(...)
]
With the payload approach, you will have something like that:
POST /group/users
{
"action": "renumbering",
"list": {
[
{ "id": "userId1", "sequence": "newSequenceNumber1" },
{ "id": "userId2", "sequence": "newSequenceNumber2" },
(...)
]
}
}
Hope it helps you,
Thierry

Semantically speaking, the HTTP PATCH method is the right way to go. This is also described in the currently chosen answer.
PATCH /group/users
[
{ "id": "userId1", "sequence": "newSequenceNumber1" },
{ "id": "userId2", "sequence": "newSequenceNumber2" },
...
]
However, the second method described in the chosen answer is not restful, because you invented new verbs inside a POST request. This is SOAP, not REST.

You can use both PATCH and POST on the URIs. I'd use PATCH if I were you. It's the best solution for bulk updates.

Related

Response tag with URL parameter

Is there any way in an Insomnia request to use the response body of a sub-request in the request body, and at the same time specify a URL parameter for that sub-request?
Consider an API with endpoints like these:
[GET] _.base_url/customers/{id}
[GET] _.base_url/products/{id}
[POST] _.base_url/invoices
When POSTing to the invoices endpoint, the API expects a JSON body along these lines (highly simplified, of course):
{
"date": "2022-03-31",
"currency": "USD",
"customer": {
"id": 123,
"name": "Something Corp.",
"address": "Here, there and everywhere"
},
"items": [
{
"id": 456,
"description": "Thingamajig",
"price": 11500
},
{
"id": 789,
"description": "Doodad",
"price": 23900
}
]
}
That is, it expects the customer field and each of the items fields to be full objects, not just IDs. (And yes, that’s stupid and not RESTful, but I don’t control the API.)
The proper way to retrieve such objects would obviously be to call the two GET endpoints with the IDs as part of the URL, and then using the responses as response tags in the POST request – but I cannot find a way to do this.
There’s a lengthy GitHub discussion on per-request veriables which has so far not produced any results, but even the suggested PR in that thread doesn’t seem like it would support defining the URL parameters when calling the request, rather than in the request itself.
Is there some way to achieve this in Insomnia?

REST API filter options in response body

I'm trying to display the remaining possible filter options, for a REST endpoint, based on the already set filters.
Is there some kind of best practice, on how to design a REST API, to let the client know about the remaining options?
Something like this came to mind.
{
"count": 131,
"next": "2",
"previous": null,
"filters": {
"status": [
1,
2,
99
],
"...": [
"..."
]
},
"results": [
{
"id": 1,
"status": 1,
"...": ".."
},
{
"id": 1,
"status": 2,
"...": "...."
}
]
}
The best way to let the user know which filters can be added to an endpoint is to create an API Documentation with Swagger or Redoc.
To let the clients know what filters they can add for an endpoint, you send them the documentation where they can see exactly how it can be used.
Do you have another particular use case for which you want to send them on the response?
If you must necessarily send the filters in the body, you can create a FilterMixin and put it on all the ViewSets where you want to have this functionality and overwrite the list function. An example would be somthing like this:
class FiltersMixin:
def list(self, request, *args, **kwargs):
data = super().list(request, *args, **kwargs)
remaining_filters = list(set(self.filter_fields) - set(self.request.query_params.keys())
return {"filters": remaining_filters, **data}
This works if the filters on the viewset are added on the ViewSet using the filter_fields property.

Validate referential integrity of object arrays with Joi

I'm trying to validate that the data I am returned it sensible. Validating data types is done. Now I want to validate that I've received all of the data needed to perform a task.
Here's a representative example:
{
"things": [
{
"id": "00fb60c7-520e-4228-96c7-13a1f7a82749",
"name": "Thing 1",
"url": "https://lolagons.com"
},
{
"id": "709b85a3-98be-4c02-85a5-e3f007ce4bbf",
"name": "Thing 2",
"url": "https://lolfacts.com"
}
],
"layouts": {
"sections": [
{
"id": "34f10988-bb3d-4c38-86ce-ed819cb6daee",
"name": "Section 1",
"content:" [
{
"type": 2,
"id": "00fb60c7-520e-4228-96c7-13a1f7a82749" //Ref to Thing 1
}
]
}
]
}
}
So every Section references 0+ Things, and I want to validate that every id value returned in the Content of Sections also exists as an id in Things.
The docs for Object.assert(..) implies that I need a concrete reference. Even if I do the validation within the Object.keys or Array.items, I can't resolve the reference at the other end.
Not that it matters, but my context is that I'm validating HTTP responses within IcedFrisby, a Frisby.js fork.
This wasn't really solveable in the way I asked (i.e. with Joi).
I solved this for my context by writing a plugin for icedfrisby (published on npm here) which uses jsonpath to fetch each id in Content and each id in Things. The plugin will then assert that all of the first set exist within the second.

REST: update a resource with different fields requiring different user permissions

I have an endpoint /groups
I can create a group by POSTing some info to /groups
A single group can be read by /groups/{id}
I can update some fields in the group by POSTing to /group/{id}
HOWEVER I have different fields that are needed to be updated by users with different permissions, for instance: A group might have the structure
{
"id": 1,
"name": "some name",
"members": [
{
"user_id": 456,
"known_as": "Name 1",
"user": { /* some user object */},
"status": "accepted",
"role": "admin",
"shared": "something"
},
{
"user_id": 999227,
"known_as": "Name 1",
"user": { /* some user object */},
"status": "accepted",
"role": "basic",
"shared": "something"
},
{
"user_id": 9883,
"known_as": "Name 1",
"user": { /* some user object */},
"status": "requested",
"role": "basic",
"shared": "something"
}
],
"link": "https://some-link"
}
As an example I have the following 3 operations for the /group/{id}/members/{id} endpoint:
I want only the user to be able to update his own known_as field
I want only group admins to be able to update each member's role and status fields.
I want both the user and the admin to be able to update the shared field
My options are this:
Should I allow all updates to be done by POSTing to /group/{id}/members/{id} with a subset of the fields for a member and throw an unauthorized error if they try to update a field that they aren't allowed to update?
Or should I break each operation into say /group/{id}/members/{id}/role, /group/{id}/members/{id}/shared and /group/{id}/members/{id}/status? The problem with this is that I don't want to have to make lots of requests to update all the fields (I imagine that there will end up being quite a lot of them).
So just for clarification my question is: Is it considered proper REST to do my option 1 where I can post updates to an endpoint that may fail if you try to change a field that you aren't allowed to?
In my opinion, option 1 is much better than option 2.
As you said option 2 is a waste of bandwidth.
More importantly, with option 1 you can easily implement an atomic update (update "all-or-nothing"). It should either complete successfully or fail entirely. There should never be a partial update.
With option 2 it's very likely the update can be implemented to complete some request successfully and reject another request, even if the two requests are considered a single operation.

Subscribing to an entity without specifying attributes

I'm trying to subscribe to an entity to get notifications using ONCHANGE.
The thing is that I'd like to get notified when new attributes get added or removed from the entity, otherwise said, I want a notification whenever anything changes on that entity.
Is that possible? I tried setting an empty condValues list in the query like this:
{
"entities": [
{
"type": "case",
"isPattern": "false",
"id": "Case1"
}
],
"reference": "http://localhost:1028/accumulate",
"duration": "P1M",
"notifyConditions": [
{
"type": "ONCHANGE",
"condValues": [
"Test Node 1"
]
}
],
"throttling": "PT5S"
}
But it didn't work.
PS: Note that I omitted the attributes Array to receive all the attributes on notification, this does work.
Current Orion version (0.19.0) doesn't implement such feature. However, it is planed to be done in the future (see this issue at Orion github repository).
EDIT: since Orion 0.27.0 you can subscribe to changes in any attribute. In order to do so, do the subscription omitting the condValues field (or use an empty array [] as value).