1

Say that I have a REST endpoint for a chess server. If I'm not logged in and do a GET on /games I could get all running games like:

{
   running_games: [ 
      .....
   ]
}

but if I'm logged in I could get a different representation for that same endpoint /games, something like:

{
   your_games: {
      waiting_your_move: [  .... ],
      waiting_for_opponent_move: [ ... ]
   },
   running_games: [ ... ]
}

Basically when I'm logged in from all running games I known the ones in which I'm participating, while when anonymous I can only get the whole list of undifferentiated games.

So my questions are:

  1. Is this a responsibility for the frontend? I would say maybe, but sometimes the frontend doesn't know about the user itself because encrypted JWT that is only dealt in the backend.
  2. Is it OK to return one of those representations for the same endpoint or should I created different endpoints for signed or unsigned? What are best practices or trade offs?

I got this doubt when coding this API in OpenApi 3 where uniqueness is defined solely by the pair path+verb not being able to use the authentication header parameter to point to different response body for each situation. So I either create a oneOf response that to me looks "ugly" or I create the before mentioned different paths.

Maybe this is a constraint from OpenApi 3 itself but those constraints have guided me well in the past so I don't know if this is "bug or feature".

tonicebrian
  • 413
  • 1
  • 4
  • 6

2 Answers2

3

No. It's not good.

It gives the calling code a problem. How to deserialise the response without knowing the type.

In your example though you are potentially returning the same type, but leaving off null properties. Which I guess saves you some bytes. I think I would just take a simple approach of returning empty arrays for the "your_games" bit instead.

{
   your_games: {
      waiting_your_move: [],
      waiting_for_opponent_move: []
   },
   running_games: [ ... ]
}

Now calling code doesn't have to worry about nulls. I can do foreach(i in your_games.waiting_your_move) {..} and if I'm logged in or not logged in it will be just fine.

Ewan
  • 70,664
  • 5
  • 76
  • 161
  • 1
    It is not a problem to decode this. In Haskell or Scala you can have sum types where a tag defines what goes after. In OpenApi 3 you even have the discriminator field for disambiguating the oneOf construct https://swagger.io/specification/#discriminator-object – tonicebrian Nov 02 '22 at 07:47
  • it _is_ a problem. your link clearly outlines why its a problem and the extensive steps they have had to implement with schemas and discriminator paths in order to work around it. – Ewan Nov 02 '22 at 10:25
2

This can be an entirely reasonable design. But if it causes problems with other tooling, it might not be worth it.

The meaning of a resource can be context-dependent. A classic example would be an URL like /users/me that would have very different representations for different clients. Along the same lines, it can also be reasonable and appropriate that your /games resource has different representations for different clients.

An OpenAPI schema cannot be a perfect description of your service. But that is not the point of such schemas – even imperfect descriptions have value for documentation and code generation purposes. If you have additional constraints that cannot be expressed in the schema language, you can still document them in human-readable form with the description field. For example, I'd expect that your endpoint has a result type along the following lines:

properties:
  running_games:
    type: "array"
    items: ...
  your_games:
    description: only provided for logged-in users
    oneOf:
    - type: "null"
    - type: "object"
      properties:
        waiting_your_move: ...
        waiting_for_opponent_move: ...

Having separate endpoints is totally OK as well, though. For example, you might offer a /running-games and /your-games endpoint, with the latter producing an error response for clients that are not logged in.

For deciding between those designs, I'd think about how the data will be typically used.

  • If you're developing a generic API, I'd probably keep those concepts separate – it's probably unnecessary to receive information for all running games when the client just wants to update the user's running game. But in case you have many of these “I don't know if the client is going to need this” fields, different API approaches such as GraphQL might be helpful.
  • If this is just a backend for a frontend you're developing, it's perfectly fine to tailor the API to your frontend's specific needs. Making a single request is often easier and more efficient than having to dispatch multiple requests. Having an optional field can be very easy to handle in the frontend. For example, the presence of this field could cause some additional UI widget to be rendered.
amon
  • 132,749
  • 27
  • 279
  • 375