RESTful API and calling remote methods (RPC) - rest

So i'm doing a bit of research on RESTful APIs and i'm trying to get some clarification on how to call methods on resources (essentially RPC).
So if I wanted to call a method (cancel) on a resource (an order), from what I can see so far I can do it one of two ways:
POST /api/1/orders/123/cancel { }
or
POST /api/1/orders/123 { 'action' : 'cancel' }
Hopefully that makes sense?
Personally I like the former more, you can just type out the url and call the method, the later requires a little extra work with POST params. Any params required by the method would go inside the POST?
I guess a 3rd is by doing:
PATCH /api/1/orders/123 { 'state' : 'cancelled' }
But I'm trying to keep an order's (object's) fields and methods separate.

The best option is number four, PATCH the order setting the status field of the resource to 'cancel'
PATCH /api/1/orders/123 { 'status' : 'cancel' }
or maybe
PUT /api/1/orders/123/status cancel
The latter has the advantage of being very easy to link to and use with a html form.

Related

{guzzle-services} How to use middlewares with GuzzleClient client AS OPPOSED TO directly with raw GuzzleHttp\Client?

My middleware need is to:
add an extra query param to requests made by a REST API client derived from GuzzleHttp\Command\Guzzle\GuzzleClient
I cannot do this directly when invoking APIs through the client because GuzzleClient uses an API specification and it only passes on "legal" query parameters. Therefore I must install a middleware to intercept HTTP requests after the API client prepares them.
The track I am currently on:
$apiClient->getHandlerStack()-push($myMiddleware)
The problem:
I cannot figure out the RIGHT way to assemble the functional Russian doll that $myMiddleware must be. This is an insane gazilliardth-order function scenario, and the exact right way the function should be written seems to be different from the extensively documented way of doing things when working with GuzzleHttp\Client directly. No matter what I try, I end up having wrong things passed to some layer of the matryoshka, causing an argument type error, or I end up returning something wrong from a layer, causing a type error in Guzzle code.
I made a carefully weighted decision to give up trying to understand. Please just give me a boilerplate solution for GuzzleHttp\Command\Guzzle\GuzzleClient, as opposed to GuzzleHttp\Client.
The HandlerStack that is used to handle middleware in GuzzleHttp\Command\Guzzle\GuzzleClient can either transform/validate a command before it is serialized or handle the result after it comes back. If you want to modify the command after it has been turned into a request, but before it is actually sent, then you'd use the same method of Middleware as if you weren't using GuzzleClient - create and attach middleware to the GuzzleHttp\Client instance that is passed as the first argument to GuzzleClient.
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Command\Guzzle\GuzzleClient;
use GuzzleHttp\Command\Guzzle\Description;
class MyCustomMiddleware
{
public function __invoke(callable $handler) {
return function (RequestInterface $request, array $options) use ($handler) {
// ... do something with request
return $handler($request, $options);
}
}
}
$handlerStack = HandlerStack::create();
$handlerStack->push(new MyCustomMiddleware);
$config['handler'] = $handlerStack;
$apiClient = new GuzzleClient(new Client($config), new Description(...));
The boilerplate solution for GuzzleClient is the same as for GuzzleHttp\Client because regardless of using Guzzle Services or not, your request-modifying middleware needs to go on GuzzleHttp\Client.
You can also use
$handler->push(Middleware::mapRequest(function(){...});
Of sorts to manipulate the request. I'm not 100% certain this is the thing you're looking for. But I assume you can add your extra parameter to the Request in there.
private function createAuthStack()
{
$stack = HandlerStack::create();
$stack->push(Middleware::mapRequest(function (RequestInterface $request) {
return $request->withHeader('Authorization', "Bearer " . $this->accessToken);
}));
return $stack;
}
More Examples here: https://hotexamples.com/examples/guzzlehttp/Middleware/mapRequest/php-middleware-maprequest-method-examples.html

RESTfully change operation behaviour

The Situation:
Via POST operation, users can create a new resource based on given parameters. If there already exists a resource created from these same parameters, the existing resource is returned instead.
Users are able to GET this resource if they know the resource ID (generated on creation, and is effectively random). I would like to provide users a way to check existence only knowing the creation parameters and without creating a new resource.
The Question:
Would it be RESTful to take some kind of "just-checking" property in the POST body to prevent a new resource from being created?
An Example:
POST vehicle
{
colour: 'red',
wheels: 4
}
201: {
vehicleId: '314-159',
colour: 'red',
wheels: 4
}
GET vehicle/314-159
200: {
vehicleId: '314-159',
colour: 'red',
wheels: 4
}
POST vehicle
{
colour: 'red',
wheels: 4,
check: true
}
200: {
vehicleId: '314-159',
colour: 'red',
wheels: 4
}
POST vehicle
{
colour: 'blue',
wheels: 8,
check: true
}
404: Not Found
Edit
Much of the discussion has been around whether the POST operation should be idempotent, which, while valid, does not address my question.
I would like to provide my users with a way to validate the existence of a resource based only on the properties that would be used to create the resource.
The idempotency of the POST method is irrelevant. What suffers from the absence of this check is subsequent GET requests which will contain a number of resources that are never intended to be used, and make it more difficult to find useful information.
A POST request containing a "do-not-create" flag would fill this need, but may not feel RESTful.
How about implementing an idempotent post? In doing so you could avoid the “check” body param.
2 ideas:
Use PUT and natural keys
One option (not sure if this works for you) is to not use some database-id in the url but use something that's a bit more like a natural key.
So instead of POSTing on some collection, you just PUT the item:
PUT /vehicles/colour/blue/wheels/8
PUT can also be used for creation just fine. And you could use a header such as this to prevent overwriting existing values:
If-None-Match: *
Don't put it on the client to do this
What if a POST for creating an item is identical to updating it? Or, what if you call POST on an existing item, it just doesn't actually do anything.
Maybe the client doesn't need to know if it just created a new item, or if the server already had that item.
Just make sure that for those 2 cases the server behaves the same, and you should be good.
Users are able to GET this resource if they know the resource ID (generated on creation, and is effectively random). I would like to provide users a way to check existence only knowing the creation parameters and without creating a new resource.
How would you do it with a web site?
Probably, with a form, that would accept as inputs the same creation parameters. The user is in effect performing a search, which is a semantically safe operation, so the form would likely use the GET method and have the arguments from the form encoded into the query string.
The endpoint, on receiving that request, could redirect it to the appropriate resource (if one already exists) or to another resource to handle the case when it doesn't.
Would it be RESTful to take some kind of "just-checking" property in the POST body to prevent a new resource from being created?
Sure - again, how would you do this on a web site? The form would have an extra checkbox, set to the correct default behavior, but giving the user the option to change it before submitting the form.
Because switching the check box changes the semantics from a safe operation to an unsafe operation, you might want to change the method on the form during submission -- HTML by itself doesn't do that, but you can do it with javascript aka code on demand.
Using POST for safe operations isn't ideal, because generic components can't tell that the operation is safe. This means that they can't know to automatically retry the request if the response is lost, they don't have the correct default cache behaviors, and so on.
For the record, the solution chosen was to add options for a special case on the GET method.
As touched on in this answer, it is not quite in the spirit of the POST method to perform this type of operation, and it muddies the model being presented to the users.

Best approach for updating a relation to another resource in a REST API

Let's say I have a REST API adhering to basic HATEOAS principles. Items belong to a User.
GET /item/13
{
id: 13,
name: 'someItem',
type: 'someType',
_links: [
{
rel: 'user',
href: '/user/42'
}
]
}
Now I need a way to change the user for a given item. Using either a PUT or a PATCH, which is the preferable way of performing that modification?
Establish the new relation by setting the id of the new linked resource as a simple property in the JSON body
PATCH /item/13
{
userId: 43
}
Establish the new relation by having the client pass the link itself as the input
PATCH /item/13
{
_links: [
rel: 'user',
href: '/user/43'
]
}
I usually think of links as read-only representations of relations stored in other formats (such as id:s to other resources), returned from GET calls. It doesn't feel very natural to me to have links as input to POST/PUT/PATCH calls, and the fact that links is an array makes it even stranger (should you be able to update all links? One single link?), but I have seen it suggested in various articles. Is there a best practice? What would be the benefits of using the links approach?
The point of REST is (at least one of them) is to make everything visible through a standard interface. In other words, if the 'relations' are a thing, than it too should have its own resource.
The API should also be more descriptive. This might be subjective, and I don't know all the details of your model/design, but 'items' don't have 'links'. 'Items' might instead have a single 'owner'. If this is the case, it might look something like:
GET /item/123/owner
So POSTing or PUTing an URL of a user (or some simple representation) would 'change' the owner of the item. It might be not allowed to DELETE the owner, depending on if the model allows unowned items.
Note, that the representation under "/item/123" would in this case have to link to "/item/123/owner", since the client only follows links it gets from the server.
So, think about what are important 'things', all of those should have a resource. Also, try to add as much 'meaning'/semantics as you can. The relation should not be called 'user', it should be called 'owner' (or whatever the meaning should be in your model).

REST API using GET Params

Say we have the following server resource:
api.example.com/event/1
Which returns some arbitrary resource, say:
{
id: 1,
details: {
type: 'webinar',
....
},
attendees: [
{
user_id: 1,
first_name: 'Bob'
...
},
...
]
}
It might be useful for a client to make a request to get just the event details of the event but not the list of attendees.
Is it better to provided two separate URLs for the resources and force two separate requests if a client wants both resources?
api.example.com/event/{event_id}
api.example.com/attendees/{event_id}
Or is it better to offer the same two endpoints, but optionally have the first one support a GET param to toggle the attendee listing on or off
api.example.com/event/{event_id}?listAttendees={true|false}
api.example.com/attendees/{event_id}
Where the listAttendees parameter will either have the representation return the attendee list or not.
Is it an common practice to allow GET params to change the representation returned from a specific URL?
I'd say the most correct way to do that in REST would be with different media-types, or media-type parameters, but since most people don't use custom media-types, I often use something I call the zoom protocol. The idea is that you have a zoom or expand parameter, with a numeric value, and it recursively includes the children entities, decreasing the parameter until it reaches zero.
So, a request like:
GET api.example.com/event/1
Returns the plain representation for the event resource, without embedding anything. A request like:
GET api.example.com/event/1?zoom=1
Would include the immediate children of event, in your case, the atendees. Following on that:
GET api.example.com/event/1?zoom=2
Would include the immediate children of event, the immediate children of atendees.
To answer your question, in REST the whole URI is an atomic identifier, so the parameters are part of the URI. That can be a problem if you're using something that won't interpret URIs in the same way, like old cache servers who won't cache URIs with a querystring.

How to call DELETE request method in Yesod?

In the book on routing ( http://www.yesodweb.com/book/routing-and-handlers ) there's a paragraph:
A separate handler for each request method will be the same, plus a
list of request methods. The request methods must be ALL CAPITAL
LETTERS. For example, /person/#String PersonR GET POST DELETE. In this
case, you would need to define the three handler functions getPersonR,
postPersonR and deletePersonR.
Performing something like
curl -X DELETE localhost:3000/person/1
works, so the server is capable of handling these requests.
Several examples (like https://github.com/snoyberg/haskellers/blob/master/routes and http://pbrisbin.com/posts/posts_database ) use GET or POST requests (in stead of DELETE) to handle this.
Is there a straight-forward way to call the DELETE request from Yesod-code? So that the route handler deletePersonR gets called?
Unlike the GET and POST methods, which can be accessed using plain links or forms on a page, DELETE methods require using JavaScript, and are not supported by all browsers. This is why POST is often used instead. To invoke a DELETE method from JavaScript, the easiest way is to use a JavaScript framework such as jQuery:
$.ajax({
url: "/person/1",
type: "DELETE",
success: function(html){
alert("Ok, deleted");
}
});