Gimly
Gimly

Reputation: 6175

REST resource returning different object depending on state

I'm trying to define a REST API and I'm having trouble with one requirement. I have an action that the API user can do that is the same thing, but can be done in two different ways.

For example, say my user uses my API to change the intensity of a light. I will have an URL something like

api/light/intensity

One option the user has to change the intensity is to set as a % of the maximum luminosity, the other option is setting the intensity as an exact value, in lumens (there is a detector for that) and he can pass the "precision" that can be low, medium and high (it changes the time it takes to get to the correct intensity).

I want the user to be able to GET the current intensity, meaning in which mode he is and depending on the mode, the % or the value in lumens and the precision.

This is where I'm lost, my GET will return a JSON object for example, is it OK to send something like

{
   "Mode" = "Percent",
   "Percent" = 50.5
}

when I'm in "percentage" mode and

{
   "Mode" = "Exact",
   "Lumens" = 200,
   "Precision" = "High"
}

When I'm in "lumens" mode?

If that seems OK, how would I tell the user which type of "object" he should parse?

What would be the best way to let the user send his changes? I was thinking about having two URL, one for each mode, like PUT /api/light/intensity/exact and PUT /api/light/intensity/percent

And both being waiting for JSON objects similar to the ones above, without the Mode.

Upvotes: 7

Views: 4778

Answers (2)

Palpatim
Palpatim

Reputation: 9272

The specifics will depend a bit on your API, and the needs of your users. The same GET method call to a RESTful API should always return the same value: a representation of the resource as defined by the information in the URL and nothing else. If you're maintaining state in the system, you're violating a precept of REST. (Edit: as pointed out by Gimly, that statement is unclear. It's not a violation of RESTful design for the system to maintain its own internal state, especially if a request changes the state of the system with a PUT, POST or DELETE. It's a violation for a request to rely on that state to return a representation of the resource, or to request a state change. Each request should be self-contained.)

I'd use a query string to change the format of the representation:

GET /api/light/intensity
GET /api/light/intensity?f=percent

That way /api/light/intensity always refers to the same resource (defaulting to the "exact" representation, which has the most data), and the query string "filters" the representation, similarly to a search query. It removes some data (in this case, the exact luminosity and precision) in favor of a relative representation in percent of some maximum value. Alternately, you could think of it as controlling the output format: GET /foo.json vs GET /foo.xml. The resource is the same, but the representation differs.

For updating a resource, you can take an object as you've described. Your server will have to understand the different formats, but you could either PUT to the bare URL, or again use a query parameter to control the format expected by the server, and then let your payload be more abstract, using value instead of lumens or percentage:

PUT /api/light/intensity
  Payload: {"value": 200, "precision": "high"}

PUT /api/light/intensity?f=percent
  Payload: {"value": 50.5}

That allows you to structure the API for your light resource in such a way that intensity is one property of the resource. "Percent" then becomes a convenience representation in the output, so when you return the entire light resource, it would read something like:

"light": {
  "name": "the light",
  "id": 12345,
  "intensity": 200,
  "max-intensity": 400,
...
}

So the API user could calculate current percent based on intensity and max-intensity. (You could of course substitute "percent" for "max-intensity" and let the user do the math the other way, but it feels more natural to me to provide absolute values and let the math calculate relative values.

Edit

Please see Tichodroma's answer for the better way of handling this. I'm leaving the answer because the discussion in the comments was useful to me, and may be useful to others in the future.

Upvotes: -1

user1907906
user1907906

Reputation:

Use HTTP Content negotiation. This allows:

  • the client to tell the server what representation of a resource it wants to GET,
  • the server to tell the client what representation of a resource it returns to the client,
  • the client to tell the server what represenation of a resource it is PUTing to the server.

Define two vendor content types:

  • application/vnd.com.example.light.intensity.percentage+json
  • application/vnd.com.example.light.intensity.lumens+json

The client tells the server which of both it wants:

GET /api/light/intensity/
Accept: application/vnd.com.example.light.intensity+percentage

The server responds:

200 OK
Content-Type: application/vnd.com.example.light.intensity+percentage

{
   "Percent" = 50.5
}

The client wants to change the intensity:

PUT /api/light/intensity/
Content-Type: application/vnd.com.example.light.intensity+percentage

{
   "Percent" = 42.7
}

The server knows from the Content-Type header how to interpret the JSON body. In this example it handles the request as in 'Percent' mode.

If the second content type was used, client and server would know to interpret the request/response as in 'Lumes' mode.

Edit: Note that the GET and PUT request use the same URL because the requests are about the same resource: the light intensity. All that differs is the representation of this resource. The proper way to handle this are content types.

Upvotes: 8

Related Questions