How to create client APIs for operations with different return types

630 views
Skip to first unread message

Stephen Broberg

unread,
Jan 13, 2016, 9:53:34 PM1/13/16
to Swagger
We're using the swagger codegen to generate our client interfaces (C++ and python).  The python code generated by the default generator works fine, we had to create a custom C++ api based off the original Qt5 one provided by swagger.

However, I'm having difficulty figuring out how to create a proper interface for operations defined in our yaml file, that look like this:

paths:
  /devices/{id}:
    get:
      summary: Get devices
      parameters:
        - name: id
          in: path
          description: ID of device to get
          required: true
          type: integer
          format: int64
      responses:
        '200':
          description: Device response
          schema:
            $ref: '#/definitions/Device'
        default:
          description: Unexpected error
          schema:
            $ref: '#/definitions/Error'

As you can see, the 200 response returns a Device object in the response, but the default (error) response returns an Error object.  The swagger codegen tool has a routine (not intended to be customized) that determines what the return type for a message should be based on finding the last response that starts with 200 and has a schema defined.  In this case, the function that corresponds to the operation above would look like:

const SWGDevice* devicesIdGet(qint64 id);

In python its similar - essentially get function that returns a Device object.

So what happens in the code path where we return an error?  The Error object is essentially lost - there's no place for it to "go".

I've been arguing with the team responsible for specifying the API that having an operation with different response objects makes it difficult to implement a generated interface, unless we want to just expose the JSON objects in the response directly, and the client has to know how to interrogate it for information when the response is something other that 200.  This seems to defeat the whole point of having an API generated from the swagger spec.

So, am I missing something when it comes to creating a usable client API, or is our spec team wrong when it comes to specifying how to return error code/message results in a RESTful way?

Ron Ratovsky

unread,
Jan 13, 2016, 10:48:42 PM1/13/16
to swagger-sw...@googlegroups.com
Well, hello there Stephen!

Let’s start off by saying this – there’s no need to argue! We can solve it all calmly and peacefully. Eventually you’ll even all go out and have a drink together, look back and laugh at how things went.

I won’t go into the code generation part, will leave that to Tony or William but I can assist with the intent of the spec.

So you went ahead and decided to use `default` to describe a generic failed response. 
Now technically, `default` is not the generic error response, but rather what the client should expect for all http codes that are not described. Yes, that can actually mean successful ones as well. In many cases it doesn’t make sense to return more than one 2XX response, but that’s the meaning of it.

What the codegen should be doing is assume that if the HTTP code is 200, it should call one function that accepts the Device, and one that is called for all other HTTP codes that accepts the Error. What happens if no Error is actually returned from the server (for example in case of a 5xx), well that should be handled in a generic well as well, to make the client error-proof.

API-design-wise, it makes perfect sense to use some sort of an Error entity, at least to me, and I try to encourage people to that. At minimum, the Error entity should include an error code and a message. The error code should allow the client to add additional business logic (that’s out of scope for Swagger) and the message, well, that’s something for the logs or to pass on to the user (depending how you manage your L10N/I18N, if you need that). Keep in mind that while HTTP status codes can cover some cases (404 is fairly simple), some simply don’t provide info. Take 400 for example on a faulty user input – that won’t help the user besides telling them they did something wrong. Personally, I wouldn’t try to adhere to the ‘RESTful ways’ as rules, but rather as a guideline (which is fairly good). Keep things in the spirit, but make your API usable. 

--
You received this message because you are subscribed to the Google Groups "Swagger" group.
To unsubscribe from this group and stop receiving emails from it, send an email to swagger-swaggers...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Stephen Broberg

unread,
Jan 18, 2016, 1:34:09 AM1/18/16
to Swagger
Thanks for your reply, Ron, but I'm still unclear how it applies to my case.

For example, you say:


> What the codegen should be doing is assume that if the HTTP code is 200, it should call one function that accepts the Device, and one that is called for all other HTTP codes that accepts the Error.

First off, the codegen templates currently provided do not generate an API that looks like this.  For a given Get operation, for example, it looks like what I posted originally - a function that returns just one type.  In fact, the Qt5cpp generator doesn't even return errors - it swallows them completely, which is why we had to write our own generator.  But since the swagger generator engine chooses only one response return type from all return types, you cannot have a function that returns different types for different Http result codes (I didn't include it here, but another one of our operations is defined as returning Foo for 201 and Bar for 203).

From a C++ perspective (or any other imperative programming language), the only way to have an API call different functions, passing in the appropriate return type, would be to pass in function pointers or lambdas to the function, one for every possible return type.  This would be a pretty horrible API, in my opinion, as it would make the control flow in the client application very difficult to follow.

A different option would be to have IN/OUT (e.g. pass-by-reference) parameters for all the return types, with only the valid one populated depending on the return type.  This would be somewhat workable, but still fairly bad programming practice (ideally, you want all your parameters to be read-only, not side-effects).  Both this option and the previous option are not really possible to implement with the current swagger codegen engine, since we only have access to using mustache files as templates and we are very limited in how we can expand the mustache/json pair into generated files (for example, we can't iterate over the set of classes for all responses in an operation and conditionally instantiate generated code depending on the type of the response return value).

A third option, would be a class which has as members all the possible return values for the function; this is essentially the same as the previous option, in terms of knowing which part of the returned object should be used based on the Http status - still not ideal.

As for the generic case of the error object, that is probably less of an issue; I can make the error object part of the basic class that holds all the other standard Http header information; this will work as long as the team agrees to use the same standard error object for all return values (so that the generator can populate it consistently).

It seems to me the real solution - at least the one that the swagger code generator is supporting (and therefore advocating) is that all possible responses (other than errors) for an operation must return the same class (or return nothing), and that all errors for a given spec be of a single type.

To unsubscribe from this group and stop receiving emails from it, send an email to swagger-swaggersocket+unsub...@googlegroups.com.

Ron Ratovsky

unread,
Jan 18, 2016, 8:06:20 PM1/18/16
to swagger-sw...@googlegroups.com
Keep In mind that when it comes to generated code, it doesn’t have to be ‘pretty’ code, but it needs to be functional.
That said, putting aside code generation altogether, how would you handle the code as a client?
Take that idea, and if the codegen currently doesn’t allow you that flexibility, feel free to open a ticket (or tickets) to expand the flexibility to better serve your needs. If it makes sense in general, there’s no reason it would not be implemented (and as usual, PRs are welcome).

To unsubscribe from this group and stop receiving emails from it, send an email to swagger-swaggers...@googlegroups.com.

tony tam

unread,
Jan 18, 2016, 8:10:57 PM1/18/16
to swagger-sw...@googlegroups.com
As Ron said, happy to help improve the templates.  The best way to move them forward is to give an example of how the client would want to interact with the request, and how each of the different responses are handled.

I do agree that a lambda function for each response code makes the most sense.  The syntax to configure that, though, is a little unclear to me without getting very ugly.
Reply all
Reply to author
Forward
0 new messages