Openapi-specification: Allow for multiple response schemas for each response code

Created on 9 Feb 2015  ·  79Comments  ·  Source: OAI/OpenAPI-Specification

Example

  responses:
    200:
      description: An array of events
      schema:
        type: array
        items:
          $ref: '#/definitions/Event'
    200:
      description: |
        An array of flattened events. This is the response
        object when singleEvents is True.
      schema:
        type: array
        items:
          $ref: '#/definitions/EventFlat'
    default:
      description: Unexpected error
      schema:
        $ref: '#/definitions/Error'

Most helpful comment

Also would vote for adding multiple responses with the same code. The best example would be any 400 (Bad request) error which can be caused by many reasons. Take in example validation errors: missing fields, extra fields passed or simply wrong data provided - these all are different errors grouped under the same status code where exact error is returned in response playload, which may vary for each situation. This is still considered as deterministic behaviour as it is uniquely defined in response playload.

All 79 comments

I doubt we'll support that any time soon. One of the leading principles we have behind Swagger is API determinism. By saying that a single response may return various object types, that pretty much breaks that principle.

Standing on idealism on this point can, ironically, lead to less determinism: Because Swagger doesn't allow multiple return values, one has to code APIs that need this capability to return generic responses.

Generic responses don't necessarily mean less determinism (though they can, depending on the level of abstraction). That said, I could argue it may imply a faulty API design in general, in many case ;)

You could. :) However, as I'm sure you're aware, that's a rigid and isolating argument that excludes Swagger from being able to support many APIs (and API styles) that exist in the real world.

I do think it's worth exploring how it could be supported in the future. But I will add that, the goal of swagger is not to describe every single API case out there. Some will be left behind, for the sake of efficiency in the ones that we can support with a full toolchain.

It's a matter of finding the balance between requirements, support, elegance and much more. The spec has evolved over the years and obviously supports today much more than it had initially.

My main concern with breaking away from the determinism principle is that it'll lead to changes all across the spec, and may increase its complexity substantially. This is not just an issue for the spec itself but also for the support tools. If we do it, we need to do it carefully and take it all under consideration.

Just to support this request.
In my API, the "get user" endpoint (GET /users/{user_id}) returns the User object if the requested user is the current user and it returns a smallest "PublicUser" object otherwise.
The format of the URL seems semantically correct for the two types of object returned.

@sebderenty - But how would you "document" these two use cases? How can you generate both client and server code that can behave this way from a Swagger definition?

I have a similar problem/concern. We recently updated the version of swagger-tools we are using which moved us from version 1.2 of the Swagger schema to version 2.0. This caused my API spec to no longer validate correctly. I have a URL path that looks like this:

GET /quote/{citvId}/group/{groupId}

Since this is a front end for a third party service, the error information I am able to provide is quite different for an invalid/unrecognized citvId value versus an invalid groupId value. In either case, I need to return a 400 status code. I originally had my responses defined like this:

"responses": {
    "200": {
        "description": "Successful response",
        "schema": {
            "$ref": "#/definitions/GroupDetailed"
        }
    },
    "400": {
        "description": "Unrecognized quote or group ID",
        "schema": {
            "oneOf": [
                {
                    "$ref": "#/definitions/UnrecognizedQuoteId"
                },
                {
                    "$ref": "#/definitions/UnrecognizedGroupId"
                }
            ]
        }
    }

and in the definitions:

"UnrecognizedQuoteId": {
    "required": [
        "error"
    ],
    "properties": {
        "error": {
            "type": "object",
            "required": [
                "httpStatus",
                "dateTime",
                "enservioStatusCode",
                "enservioMessage"
            ],
            "properties": {
                "httpStatus": {
                    "type": "integer",
                    "description": "This will be 400 for this type of error"
                },
                "dateTime": {
                    "type": "string",
                    "description": "ISO format timestamp for request"
                },
                "serviceStatusCode": {
                    "type": "string",
                    "description": "Numeric status code returned by third party service (e.g. 1106)"
                },
                "serviceMessage": {
                    "type": "string",
                    "description": "Message from third party service"
                }
            }
        }
    }
},
"UnrecognizedGroupId": {
    "required": [
        "error"
    ],
    "properties": {
        "error": {
            "type": "object",
            "required": [
                "citvId",
                "httpStatus",
                "dateTime",
                "validation"
            ],
            "properties": {
                "citvId": {
                    "type": "string",
                    "description": "The citvId that was specified in the request."
                },
                "httpStatus": {
                    "type": "integer",
                    "description": "This will be 400 for validation errors"
                },
                "dateTime": {
                    "type": "string",
                    "description": "ISO format timestamp for request"
                },
                "validation": {
                    "type": "string",
                    "description": "Message indicating the unrecognized group ID"
                }
            }
        }
    }
},

Since responses no longer allow oneOf for the schema, I have had to collapse these two errors into a single error like this:

"responses": {
    "200": {
        "description": "Successful response",
        "schema": {
            "$ref": "#/definitions/GroupDetailed"
        }
    },
    "400": {
        "description": "Unrecognized citvId or groupId",
        "schema": {
            "$ref": "#/definitions/UnrecognizedQuoteOrGroupId"
        }
    }
}

with the definitions collapsed like this:

"UnrecognizedQuoteOrGroupId": {
    "required": [
        "error"
    ],
    "properties": {
        "error": {
            "type": "object",
            "required": [
                "httpStatus",
                "dateTime",
            ],
            "properties": {
                "citvId": {
                    "type": "string",
                    "description": "The citvId that was specified in the request in the case of a valid citvId but igroupId."
                },
                "httpStatus": {
                    "type": "integer",
                    "description": "This will be 400 for this type of error"
                },
                "dateTime": {
                    "type": "string",
                    "description": "ISO format timestamp for request"
                },
                "serviceStatusCode": {
                    "type": "string",
                    "description": "Numeric status code returned by third party service (e.g. 1106) for an invalid citvId"
                },
                "serviceMessage": {
                    "type": "string",
                    "description": "Message from third party service for an invalid citvId which translates to their risk ID"
                },
                "validation": {
                    "type": "string",
                    "description": "Message indicating the unrecognized groupId in the case of a valid citvId but igroupId"
                }
            }
        }
    }
},

My API is behaving in a completely deterministic manner. If the citvId is invalid, one type of error information is available. Alternatively, if the groupId is invalid, slightly different type of error information is available. In order to provide the various information in a single response definition, I have had to introduce ambiguity into my definitions by removing the uncommon properties from the required list and explaining when certain properties will be available in the response. In my opinion, this is less deterministic and more confusing than when oneOf was available to allow for multiple schemas for the 400 response.

I believe that restricting the responses to a single schema per status code is overly rigid and restrictive.

Please do not respond by simply saying, _"I could argue it may imply a faulty API design in general"_ as you did to a previous post. If you feel that this API design is faulty, please explain the correct way to handle a situation like my case.

@dannylevenson-ps - thank you for the elaborate use case, much appreciated.

To get it out of the way oneOf was never supported. It wasn't supported in 1.2 nor in 2.0. There may have been a bug in the JSON Schema which allowed for it in swagger-tools, but that does not mean it was supported. The source of truth is the spec itself and it's clear oneOf is not part of it.

You're right about the API behaving in a deterministic manner, but it behaves in a deterministic manner to the producer (i.e. the server). At the end of the day, the API (and its documentation) aim to serve the consumer (i.e. the client), and from the client's point of view, it's a non-deterministic behavior. The client has no way of knowing that citvId or groupId are invalid. Had it known that, I imagine it wouldn't have tried to invoke the API call in the first place - so it has no way of knowing what error to expect. In fact, by using oneOf, you're saying "you'll be getting this OR that" which is... non-deterministic.

If your errors had a shared property that has a unique value per error, that could have acted as a discriminator and you could have used inheritance to document it. Unfortunately, httpStatus in your example, while shared, has the same value for both errors.

I do have some ideas how to deal with situations that are not fully deterministic, but that's more of a general solution and not specific here. Unfortunately, it's far from being baked but I'll get around to it as well.

FWIW, the comment "I could argue it may imply a faulty API design in general" was a bit out of frustration and not directed at anyone in particular. I don't think it's our job here to comment on one API paradigm or the other. Just like anyone else, I have my opinions and can share them if asked, but telling people how to design their APIs is not within the scope of our work. As @fehguy mentioned, we're never going to cover all use cases, but we definitely want to expand the spec carefully to accommodate for more.

_@webron_ - you said, _"The client has no way of knowing what citvId or groupId are invalid. Had it known that, I imagine it wouldn't have tried to invoke the API call in the first place - so it has no way of knowing what error to expect."_

In a perfect world, yes, the client would invoke this API if and only if it knew that both the citvId and groupId were valid. And, if that were true, my server code would not have to do parameter validation on the invocation for either parameter. However, in order to be robust, my server code must validate the multiple inputs for this call and the error responses need to be different depending on which value is invalid. I do not agree that this is non-deterministic from the client's point of view. If the client feeds the API an invalid citvId it gets one type of error response. If the client feeds the API an invalid groupId it gets a different type of error response. The only way it would be non-deterministic from the client's perspective is if the client could get either this or that given the same erroneous input for two different calls.

If the client is using the API properly, it should always have valid values for both, but it is unrealistic to state that a client will only ever invoke the API call with valid values for all input parameters.

It's deterministic if you combine the error with the cause (as you mention above). Since there's no way to link the error to the cause, it's becoming an either/or situation. _You_ know the link, but if, for example, there's an auto-generated client, who would it know how to handle it?

Also would vote for adding multiple responses with the same code. The best example would be any 400 (Bad request) error which can be caused by many reasons. Take in example validation errors: missing fields, extra fields passed or simply wrong data provided - these all are different errors grouped under the same status code where exact error is returned in response playload, which may vary for each situation. This is still considered as deterministic behaviour as it is uniquely defined in response playload.

You can still document it, assuming you follow a common structure to all the errors (as you probably should).

I'm getting angry all over again. I agree with Jack and and disagree with Ron. Even if you use a common structure for all errors, there are going to be cases where more or different information is available based on the erroneous input. All bad requests need to return a 400; all bad requests do not have the same error response data.

@webron it's true (to some extend), but as @dannylevenson-ps just mentioned reponse data can vary. And the purpose of api documentation is to show user what errors he can get. So how would one document different error responses (let's assume same data structure for simplification)?

Don't see why you need to get angry. We're allowed to disagree.

I'm not saying they have to return the same response data, I'm saying the _should_ have a common response _structure_.

There's no problem documenting something like:

{
  "code": 0,
  "details": ...
}

Where the details can be a string or an object or whatever you want with the specific information for that error. The code would act as the discriminator between the different error types. That would allow consumers to process such errors more easily, if they know the available codes in general, or even know to expect a basic common structure at all. Does it force a design? Absolutely. Does it cover the requirements? I believe so.

Okay, this makes sense. However, this doesn't look useful to me when i try to describe response from my route as i still can define only one object for given response code. Assume i do have unified errors where code is a discriminator. The issue i see here is that i can have 10-30 errors for different cases while given route can return only couple of specific ones. And another route will raise different subset of errors. How would one document this?
What i'm struggling to see is list of errors in the request description as we have list of status codes.

Wonder if it would make more sense to have separate section named errors and allow each route to have it's own list of returned errors taken from errors sections (in the same way as you have responses now).

You raise a valid point here. It's not that you can't define those groups right now, but you'd require to duplicate your definitions. It would be interesting to try and find a way to control it more easily, but I'd suggest opening a different issue for it, and going into more details, with an elaborate example. That would allow us to dive into the use case and see if/how we can resolve it better in the future.

I'd add that in my API, I have multiple errors that can be thrown.

For example :
404 : document not found
400 : invalid model data (validation)
400 : token invalid (missing "bearer" or base64)
401 : token expired
401 : token invalid (JWT can't parse)
500 : could not write to disk (rethinkdb)
500 : unknown errors

etc.

Highly needed, I can't generate accurate TypeScript definition for error results because of this. TypeScript allowes union types and are perfect to encapsulate differing error cases e.g.

interface Error {
    error: string
}

interface ValidationError extends Error {
    error: "ValidationError"
    data: {
        fields: {[k: string]: string[]}
    }
}

interface GenericError extends Error {
    error: "GenericError"
}

var swaggerResult: ValidationError | GenericError; // Unable to generate this!

Note: above example uses string literal types, e.g. "ValidationError", which are not common in other languages.

Edit Btw, I find this arguing silly. Union types have existed for eons they are really useful. Before this is solved the API's are replaced with GraphQL which does have union types among others.

+1 for me - I keep hitting this issue with Swagger. I have a general search service that accepts a parameter "ns". The ns determines which search is executed, and returns a result that may have different fields depending on the search configuration. I definitely don't want a service per search, as the general idea is to give flexibility by having a generic service - and I definitely don't want the reason for needing a service per search to be "the documentation tool doesn't support it" that would be disappointing. Documentation shouldn't get in the way of productivity. At the moment I can't find even a hack to document different successful search responses.

Moving forward, please suggest ways to represent it in the spec.

It looks like the goal is to have dependency between parameter _values_ and the accepted responses. I can think of ways to do that (pending #574), but that would work for simple cases. Not sure how to support more complicated dependencies and keeping some level of elegance to the spec.

For my case, I'd be happy without needing to associate the parameter with accepted responses. I can use the description of the responses to indicate to the reader which parameters apply if that's necessary. Simply removing the constraint of having only a single response definition per http response code would be fine for my case.

Bear in mind I'm using Swagger only for documentation, I'm not using any of the code-gen tools. The only things I use are hand crafted JSON, Swagger-UI to display it, and Swagger-tools to validate the JSON.

@rgilling - thanks. I agree that for human-only documentation, the solution is very simple. However, given the growing number of tools out there that rely on compliance with the spec and applying logic based on it, we can't ignore them.

Yes, I +1d here also for the documentation case. I gave up on Swagger Codegen because I don't understand how it evolves: http://stackoverflow.com/questions/36133273/update-flask-code-with-new-code-generated-by-swagger

My suggestion is to use "oneOf" like @dannylevenson-ps used above. It's part of the json schema spec after all.

Yes it's non-deterministic and consumer must use custom pattern matching on "type" or "error" property since this is JSON and there is no classes. We can't agree on single property name, it's impossible.

Following a JSON schema's "oneOf" would be probably easiest to agree.

+1 "oneOf" it's a good way to resolve that problem. I think it's the most simple to implement in OA Specification.
The example of @dannylevenson-ps it's a good example of my problem: i need to create two different 200 responses, and the schema of response change when a query boolean parameter change.

@webron

At the end of the day, the API (and its documentation) aim to serve the consumer (i.e. the client), and from the client's point of view, it's a non-deterministic behavior.

Could you elaborate the reason why is it non deterministic behavior from the client's point of view?

The oneOf definition in json-schema-validation (http://json-schema.org/latest/json-schema-validation.html#anchor88) says :

An instance validates successfully against this keyword if it validates successfully against exactly one schema defined by this keyword's value.

It seems that clients can determine one schema of a response by trying validation for all schema denoted in 'oneOf' property.

In typical error response design, an error response has 'code' or 'reason' property to specify the
type of error and it can be represented as an enum property.
If the enum value of each schame in 'oneOf' is unique, it can be used to determine a schema.

It seems to just be a difference of scope. If you were to say that the response object, as you put, is really the the set of all schema, then wouldn't that be the same as defining the response as a single object with multiple fields?

But because you don't know what the form will be by only looking at the spec, the schema is unknown (or maybe better stated: "is the superposition of all the schema") until you call the function and get a response. That is the very definition of deterministic: Can you, given the initial conditions determine the end result? In the case of REST given the request know the form of the response? And here is the answer is no.

If you take the form to be the superposition of all the schema's then that would defeat the purpose of using the oneOf designatior in the first place, since it would be the same as forcing the the response schema to be the superposition of all the schemas specificied. In that case, what's the point?

In terms of a truly hypermedia driven API, you may very well want to respond with multiple different values based on the client passing an appropriate Accept header. For example, your "products" endpoint may have two different responses - which I have seen some APIs call "shapes" - based on the given use case. You may have a "full" response that includes all possible data associated with the resource and a "brief" that will only return the basic info (i.e. name, title, description, price). Ultimately, these two responses will likely have different mime types or - better - content type profiles. So I'd ideally like to see something like:

"responses": {
     "200": [
          "mime-type": "application/json",
          "profile": "vnd.example.product.brief.v1",
          "schema": {
                    #Schema of the "brief" version of the resource here
          },
          ...
     ],
     "400": [
          ...
     ]
}

The array under the response code could also be indexed by a full mime type with profile (i.e."content-type": "application/json; profile=\"vnd.example.product.brief.v1\"") but, either way, it would allow you to define how those responses should look. I don't believe this violates the restraint of a deterministic API since the responses are based on the requested content type. It would also allow you to declare that this could return binary objects as a response - i.e. PDF files, CSVs, etc - which are valid for API resource responses. You'd likely skip the schema definition in that case, unless the response contains data objects that could be defined that way (struggling to think of any right now).

This sort of addition to the spec would help me tremendously.

EDIT: MAy also want to add a "default" boolean value to a response for the case where the Accept header is *. Though I'd suggest in that case returning a 400 error with an explanation that an appropriate Accept header is required.

I was in total disagreement with the nature of this, but given what @vrrobz is saying, I think it would be legitimate to switch the schema based solely on the media-type. I think what we are debating here is really the fact that json is two ambiguous, and debatably should not even be used as the media-type, but instead some self describing hypermedia derivative. In that case, I would expect that the schema to have a one-to-one match with the media-type, where the definitions section would be obsolete by having known global references to the actual RFC-ed real type.

I just deleted a lot of pontificating, and I'll replace it with, would I ever really return two different media-types with the same responseCode, where I couldn't just mark most of the fields as not required? The one example I could think of would be json versus xml differentiation. But given technically you could switch the format between them, there would only be a negligible amount of value added by even admitting to an xml version (since every code generator could easily compute an xml version from the json schema), why bother stating "here's the xml schema for when you call .xml or ?xml or Accept-Type= application/xml".

This is exactly my thought, though there's value in stating that you can return XML, JSON, PDF, CSV, whatever else. If the schema is the same for both your XML and JSON response, you can just reference the same schema under two separate mime types. If you're returning two different JSON payloads on a 200 response depending on the request (minus the Accept header) you're probably doing it wrong. If, however, you add a profile that defines the schema for each of those JSON responses, you're doing the client a favor by letting them know in advance what they can expect every time, though you are putting in the condition that they specify which response type they want through the Accept header.

Doing things this way opens up other possibilities - like better versioning and better separation of concerns at the resource level. If your API is a monolith, then version on the URI (i.e. api.example.com/v1/resource) seems like the easier way to go. If, instead, you're operating in a microservices environment where different teams may be responsible for the lifecycle of different endpoints, that versioning structure gets out of hand quickly (api.example.com/resource1/v1 vs. api.example.com/resource2/v3, etc.). Using profiles in the mime type means you can keep the URL structure for your resource and version at the resource level (i.e. Accept: application/json; profile="vnd.example.resource.v2"). If you're backwards compatible for N versions, you can list all of those versions on a per resource basis in your documentation.

This also opens the OpenAPI/Swagger code to be more than just documentation and testing descriptors - it allows the client to grab on a per-resource basis everything I need to know about that given resource. It starts functioning a bit more like a WSDL, but without as much overhead. Rather than grab the Swagger for the entire API, you can have tooling in place that grabs the Swagger just for the given resource you need. For example, calling OPTIONS on the resource or even just calling it directly may result in a Link header that points to the location of the Swagger definition for that resource - and only that resource. So RESTful endpoints can be responsible not only for their own state, their own data, etc. they can also be responsible for their own documentation.

You may say I'm a dreamer...

To weigh in on this lengthly discussion…

I agree that restricting the responses to a single schema per status code is overly rigid and restrictive.

I want Swagger to illustrate the real-world behavior of my API to best help my API users (customer-side developers). Of course, my API can emit a variety of 400 Bad Request responses. My API requires several parameters. Leaving any one out returns a 400 with associated JSON indicating the missing parameter. My users should be able to see these behaviors (and not just with 'Try it!')

One post states that 'the goal of swagger is not to describe every single API case out there.' That's like saying we want some users, but not others, even if those 'others' are a very large cohort that have important APIs in need of a robust documentation solution.

Just my 2-centavos…

@wparad and @webron If you allow the response schema to change based on the input request, then given the input request, you can deterministically still compute what the response schema is.

In the OP's vagueish question there was no such selector. But in the SO post I linked to (http://stackoverflow.com/questions/36576447/swagger-specify-two-responses-with-same-code-based-on-optional-parameter), a segment of which is repasted here:

I have a `GET` with an optional parameter; e.g., `GET /endpoint?selector=foo`. 
I want to return a 200 whose schema is different based on whether the parameter was passed, e.g.,:
    GET /endpoint -> {200, schema_1}
    GET /endpoint?selector=blah  -> {200, schema_2}

there is, and thus you can deterministically compute the response schema given the initial request. Thus I reject

 Can you, given the initial conditions determine the end result? In the case of REST given the request know the form of the response? And here is the answer is no.

as false.

Hi folks, thanks for the discussion here. Absolutely we need to accommodate more in the spec, because as we've seen many times, it's almost impossible to use a single schema for describing all supported content types.

There are some challenges, of course, namely _how_ you model schemas for non-json formats where input would be welcome.

I think it makes sense to reference an XSD for XML schema (JSON schema to XML schema is IMHO not viable). That is, where a response or request body allows an object type with

"schema" : { "$ref" : uri-of-JSON schema }

I propose allowing instead

"xsd" : uri-of-XML-schema

But adding this to a top level response to say "one of JSON schema A, JSON schema B, XML schema C, XML Schema D (etc)" needs work. This may require too much complexity. To deal with this, I recommend allowing separate operations (i.e. one for each separate produces=media type. (However, reuse of common query parameters etc. becomes an issue there. Thus, it is simple but verbose. #182 again)

Hey @vrrobz , I created a proposal #721 that is similar to what you were suggesting with just a slightly different structural arrangement. I use the media type as a key. If you also wanted to differentiate on profile you would need to include that in the key as a media type parameter.

"responses": {
     "200": 
         "description" : "Product Brief",
         "representations" : {
               "application/json; profile=vnd.example.product.brief.v1" : {
                     "schema": {
                            #Schema of the "brief" version of the resource here
                     },
                    "examples" : [ #Examples go here ]
                }
          } ,
          ...
     ],
     "400": [
          ...
     ]
}

Would love to hear your feed back on this alternative.

AH - I was going to comment on that in the proposal. This is awesome, thank you!

@darrelmiller It seems the example you provided would _only_ allow for multiple representations based on media-type. This seems overly restrictive, as there are several ways to provide input (including query strings) that could have an effect on the output. Was this what you intended, so I can understand?

That said, however, I really like your representations idea. Perhaps the representations key could be used as the place where different outputs are stored, regardless of what mutator caused them.

@rex Any number of factors could affect which representation is returned. It could be a query string parameter, an accept header or some server state. All that the OpenAPI description is saying is that the response could be one of those described.

However, the only way a representation can be discriminated is with the content-type of the response. For example, if you wish to returning multiple representations all with application/json as the media type, but each with a different schema, then you will need to include something like the profile parameter to distinguish between the responses.

@darrelmiller Thank you for clarifying that, I really appreciate it. :)

need "oneOf" badly :|

Just as we have multiple status codes for each endpoint and a default schema for fallback, we can also have a similar structure for each status code. We can make use of a special response header with text value on responses to use for matching the response schema.

Below is an example responses object of my proposal:

{
    "responses": {
        "200": {
            "default": {
                "description": "OK"
                "schema": {
                    "$ref": "#/definitions/SuccessfulResponse"
                }
            }
        },
        "400": {
            "No Body": {
                "description": "The body is not provided."
                "schema": {
                    "$ref": "#/definitions/NoBodyErrorResponse"
                }
            },
            "No Parameter": {
                "description": "The parameter is not provided."
                "schema": {
                    "$ref": "#/definitions/NoParameterErrorResponse"
                }
            },
            "default": {
                "description": "Unexpected client error."
                "schema": {
                    "$ref": "#/definitions/UnexpectedClientErrorResponse"
                }
            }
        },
        "default": {
            "default": {
                "description": "Unexpected server error."
                "schema": {
                    "$ref": "#/definitions/InternalServerErrorResponse"
                }  
            }
        }
    }
}

Let Response-Key be the response header we agree upon. Then, all of the following response headers would be matched with a response object deterministically:

  • Status-Code: 200, Response-Key: Null
  • Status-Code: 400, Response-Key: No Parameter
  • Status-Code: 400, Response-Key: Null
  • Status-Code: 500, Response-Key: Null

I think disadvantages of this implementation I can see, an additional complexity and some verbosity on responses object, would worth the added feature.
Server codes must be changed in this way or another if we want to support this feature with a spec, and we don't want to depend on pattern matching on the client side.


If there is not a good reason to start matching responses using status codes, we can start matching the request using our Response-Key header. This way, APIs we can't add this response header to their responses would have a simpler definition than the previous proposal.

Let me show an example again:

{
    "responses": {
        "OK":  {
            200: {
                "description": "..."
                "schema": {
                    "$ref": "#/definitions/SuccessfulResponse"
                }
            }
        },
        "No Body": {
            400: {
                "description": "..."
                "schema": {
                    "$ref": "#/definitions/NoBodyErrorResponse"
                }
            }
        },
        "No Parameter": {
            400: {
                "description": "..."
                "schema": {
                    "$ref": "#/definitions/NoParameterErrorResponse"
                }
            }
        },
        "default": {
            400: {
                "description": "..."
                "schema": {
                    "$ref": "#/definitions/UnexpectedClientErrorResponse"
                }
            },
            500: {
                "description": "..."
                "schema": {
                    "$ref": "#/definitions/InternalServerErrorResponse"
                }  
            },
            "default": {
                "description": "..."
                "schema": {
                    "$ref": "#/definitions/UnexpectedErrorResponse"
                }  
            }
        }
    }
}

In addition, in my opinion, this could be a better representation for how errors are handled in applications. We don't think of which status code we are sending in the first place. We handle error cases and accompany them with status codes before sending it to clients.

Excellent suggestion @hantuzun! I think this is an elegant and flexible solution.

@hantuzun Your suggestion is an interesting and completely valid approach, however, it is a different approach than the way that HTTP is designed to work. HTTP uses standardized status codes so that client applications can code response behaviour without having any private understanding of server design. It order to do anything useful with the response-key values a client would need to have prior understanding of the semantics of those response keys. That introduces out-of-band coupling that HTTP is trying to avoid. Personally I would like to see OpenAPI guide developers away from this type of practice, rather than towards it as it is counter to the design principles of HTTP.

My concern with both the "Response-Key" header suggestion and the only differentiated by media type header suggestion is that now OpenAPI is making arbitrary requirements on how your API works. This seems to contradict the principle that "OpenAPI does not require you to rewrite your existing API. It does not require binding any software to a service--the service being described may not even be yours."

In my particular use case, I am trying to document an API built in a third-party tool that does not let me control the schema of the internal error response, which will always be in a vendor determined XML schema. The discriminator is an XML element name rather than the value of a property. Perhaps though this API just falls under "Not all services can be described by OpenAPI", but it is unfortunate that one has to get so far along before discovering the conflict.

Hey @dannylevenson-ps @darrelmiller @lablua, thanks for the replies.

Your concerns are right on the point. I'm a user of OpenAPI spec and I'm not comfortable with its philosophy.

It's seems like we can't have all three below together to me:

  • Multiple schemas for one response.
  • Deterministic response matching.
  • Compatibility with standard HTTP APIs.

This's a hard problem to get around.

I wish HTTP standard adopts substatus codes introduced by ISS: https://support.microsoft.com/en-us/kb/943891

@hantuzun How does the following approach look to you?
You get the multiple schemas per response. The type of schema is identified using the profile parameter in the content-type header and you cover all of status codes. All 200 class responses will get a "SuccessfulResponse" and any status code between 400-499 will get either a NoBodyErrorResponse, a NoParameterErrorResponse or a UnexpectedErrorResponse.

{
    "responses": {
        "2XX": {
            "description": "...",
            "representations": {
                "application/json;profile=Success": {
                    "schema": {
                        "$ref": "#/definitions/SuccessfulResponse"
                    }
                }
            }
        },
        "4XX": {
            "description": "...",
            "representations": {
                "application/json;profile=NoBodyError": {
                    "schema": {
                        "$ref": "#/definitions/NoBodyErrorResponse"
                    }
                },
                 "application/json;profile=NoParameterError": {
                    "schema": {
                        "$ref": "#/definitions/NoParameterErrorResponse"
                    }
                 },
                 "application/json;profile=UnexpectedError": {
                    "schema": {
                        "$ref": "#/definitions/UnexpectedErrorResponse"
                    }
                 }
            }
        },
        "5XX": {
            "description": "...",
            "representations": {
                "application/json;profile=InternalServer": {
                    "schema": {
                        "$ref": "#/definitions/InternalServerErrorResponse"
                    }
                }
            }  
        }
    }
}

@darrelmiller, when you first suggested adding the "profile" parameter to the media type, I thought you were just arbitrarily adding something to the media type in the Content-Type header. I looked at RFC 2616 and now see that parameters are allowed as name/value pairs. I guess I've never really thought about those extra values you sometimes see, like charset, etc.

I think that adding one or more parameters to the media type is a very good way to specify the schema to use for the response. It allows for all three of the concerns listed by @hantuzun:

  • Multiple schemas for one response. _(my initial concern)_
  • Deterministic response matching. _(@webron's major counterpoint)_
  • Compatibility with standard HTTP APIs.

@dannylevenson-ps Also, I didn't create the notion of profile. It has a long history. It was originally introduced in HTML but never really gained any traction. People have created Internet Drafts to specify it as a standard media type parameter and media types like collection+json explicitly use profile for adding application semantics.

Technically, you are not supposed to use a media type parameter unless it is registered with the media type registration. However, I believe that is a minor violation. People use charset all the time with application/json when it is not officially defined and the spec says that it means nothing.

@darrelmiller; your proposal looks really fine to me. I didn't think about adding a parameter to content-type.

I'd prefer to have the "description" parameter for each "representation". I also guess there will be a default fallback parameter in the content-type matching.

Finally, have you proposed to match for status code classes like 2xx instead of 200 since "descriptions" parameter would look bad ugly to have just one description for most of the time. This matching can make it harder to write specs for foreign APIs though.

@darrelmiller please clarify that this does applies not only to wildcards "2XX" but also to specific response codes "200" etc.

Also, can the response headers and "example" be specialized per profile or representation?

We allow multiple different Accept headers, but do not use profiles. I like the ability to have different schema per response, keyed off a representation. Ideally, we would like the tooling (swagger-ui) to also make that association, so that when the user selects a response content type from the drop down (based on the items in the operation's "produces" set of accept media types, the UI will respond by showing the response headers and schema associated with that.

However, we do not (currently) list the error response representation media types in the "produces" list in our APIs. The use of the drop down in the swagger-ui is mutually exclusive; one can select only one media type, not a list of (weighted) response media types for true content type negotiation. But with this proposal, the tooling would be more complex; even at the 400 level, one would need to show the various representations in order to show the possible response schema and examples.

@hantuzun I'd prefer the description to be only at the response level :-) But the feedback is good. I'll add it as an open issue when I submit the PR (hopefully later today).
Default is a weird one. I need to think more about it, but it could work.

The wildcard status codes is a change that is already in 3.0. It encourages OpenAPI spec writers to cover response descriptions for all status codes as that is what HTTP client applications are supposed to handle.

@DavidBiesack Yes, it would work the same for not wildcarded statuses.
There is an examples array inside each representation object. When you define a header, does that tell the client that the header MUST be in the response or MAY be in the response? What types of headers do you see varying between different representations?

I'm not sure how tooling will adapt to this. I do believe this is closer to how HTTP is supposed to work, so hopefully adapting the UI will not be too hard.

I don't expect there to be different headers per response representation, but it seems the spec should not prohibit it. I can't say about whether the header is required or not. (I cannot find if/where 2.0 or 3.0 indicates required response headers. )

Hi @darrelmiller; thanks for the proposal. I'm excited about the progress.

I thought we may need a default content-type header, in case none of the content-types are a match. It's already available in the spec for matching status codes in OpenAPI 2:

screen shot 2016-08-18 at 09 43 22

We can discuss where we better put descriptions under that the issue you may open. This issue has got too long :)

I'd also like to invite @zeeshanejaz to this discussion though, Co-Founder/CTO of APIMATIC. We're depending on their service and wishing to do so in the future as well. It seems like they can implement this proposal as well to me.

Sorry guys, late to the party. I like @webron approach, since I too have worked on code generation and deterministic choice is plain and simple. But unfortunately, this is not going to be acceptable to the API practitioners for valid reasons as @vrrobz pointed out. @hantuzun and @darrelmiller iterated and narrowed down an approach which seems attractive. BTW, their approach can also help streaming APIs and webhooks, where a single endpoint often emits multiple schema objects.

BTW, I am no longer the CTO of APIMATIC, but definitely have friends there who I can involve to consider implementing this approach when accepted @kand617

The discriminator approach suggested in #736 is also essentially doing the same thing, I propose that we have a unified way of handling both cases (WebHook events and multiple schemas on same status code).

On a side note, let me share why I'm really looking for this this feature.

At SuggestGrid we have implemented an error handling mechanism where we throw errors with a key and optional data. Our server wrapper, then, catches these errors and returns an error message with error status code, error description, error url etc. Here's an early draft for some details: https://gist.github.com/hantuzun/e44feb103396b1c34c047c6fbac446fd

For example we throw errors like action-item-id-is-missing or action-user-id-is-missing. When they're catched we look at our errors data file and prepare an error response. Both of these error keys may have 422 status code associated for instance.

Below is a part of our errors data file:
screen shot 2016-08-18 at 13 31 54


However, since we can't have multiple responses for same status code right now, we can't implement this architecture for all responses, including success ones. That could have been so neat.

It's not a problem for our error responses though, since all of them have the same schema: error_text, error_description, and error_url strings.

With @darrelmiller's proposal we can implement a similar architecture for all of responses with our internal response keys are included in response content-type headers. I'm so excited about this feature :)

The PR for the representations object is here https://github.com/OAI/OpenAPI-Specification/pull/761
Comments encouraged.

@hantuzun : The workaround I came up with for our project -

We use an implementation of the Problem Details JSON object response in our API. We then have a child class of ProblemDetails, ValidationProblemDetails, that contains a list of errors. Then, in our Swagger attribute definition, we define the response type as a ProblemDetails, essentially ignoring the additional properties when using a ValidationProblemDetails.

So, for example, on a WebAPI PUT endpoint, the Swagger definition has three responses: 204, 400 and 404. For the 4** codes, both have the expected schema set to ProblemDetails in the swagger defintion, even though the 400 actually returns a ValidationProblemDetails. All 400 responses return the same base ProblemDetails object with the same Title, Detail, Status, etc., but they additionally include the model state/view model or domain validation errors in the list of errors.

So our 404 response would look something like:

{
  "status": "404",
  "title": "Action not found.",
  "detail": "The specified action could not be found."
}

and a 400 response due to a mismatched id:

{
  "status": "400",
  "title": "Unable to update the action.",
  "detail": "The id of the action to update did not match the id of the action provided."
}

and the 400 with validation errors:

{
  "status": "400",
  "title": "Unable to update the action.",
  "detail": "The data provided is invalid.",
  "errors": {
    "itemId": "Actions must have an item id parameter. This parameter defines...",
    "userId": "Actions must have a user id parameter. This parameter defines..."
  }
}

Note that the status and title are the same per response code. So in the swagger definition we are able to define the 400 response with a ProblemDetails and then add extra information when necessary. Even reading into the spec it allows for additional properties in the response to handle additional, optional information.

This was especially important as we are using sway in our integration tests and in order to automatically verify responses schemas we needed these values to match per response code. Sway automatically ignores the additional properties and we can validate them manually when necessary.

So for your example you could have some parent ErrorResponse defined with error-code, error-text, error-description properties defined. Then have a child class of ValidationErrorResponse that has a error-documentation property containing the validation errors like:

{
  "itemId": "Actions must have an item id parameter. This parameter defines...",
  "userId": "Actions must have a user id parameter. This parameter defines..."
}

I wanted the same thing, and I came up with a workaround that seems to work just fine:

I´ve just defined two different paths:

/path:
(...)
      responses:
        200:
          description: Sucesso
          schema:
            $ref: '#/definitions/ResponseOne'
(...)

/path?parameter=value:
(...)
      responses:
        200:
          description: Sucesso
          schema:
            $ref: '#/definitions/ResponseTwo'
(...)

I can even document each option differently and put optional parameters that only area needed on the second case toguether, making the API doc much cleaner. Using operationId you may generate cleaner names for the generated API methods.

Am I missing something, or my solution is safe when the variable that defines the response type is a query param?

Edit: I do understand it is not explicitly allowed /encouraged to do it (neither I found some place explicitly disallowing it). But as a workaround, and being the only way to document an API with this behaviour in the current specification, is there any real problem that may happen if I do it that I´m not seeing here?

Edit again: two main problems: extra query params do not work (it adds another "?" and fails) and = is URL escaped on some code gen, failing again. Looks like a fail if one needs any of it! But I will keep this here so others will know what happens if they try.

Anyway, allowing repeated paths (it would need to be required to have different operationIds) seems a reasonable way to support this kind of API. Any toughts on it?

Well no, you aren't supposed to use query parameters in your path definition.

@cristiantm thanks for this! While I agree with @fehguy, because this issue 270 is a blocker for me, I see this as a savior hackaround until this issue is resolved. Thanks!!

Well @tommyjcarpenter we have approved the inclusion of oneOf or anyOf in the schemas section. So unless I'm mistaken, you'll be covered with that in 3.0

By "issue is resolved" I mean whenever your "you'll" becomes "are" though I do not know where oneOf and anyOf was defined.

For me, looks like oneOf would also be a "workaround" for cases like mine and @tommyjcarpenter case

1) I dont see how exactly codegen will deal with it - what would the API methods return? Looks like any way to do it will lead to some very confusing generated code...
2) I would need to put in the text description what kind of input generates each schema option... less deterministic than my hackish approach.

Still, nice to see some movement on at least supporting some documentation of this cases.

In Open API 2.0, we use #tag in the paths to allow separate definitions. A hack yes, but it works. See my comment on #146 https://github.com/swagger-api/swagger-spec/issues/146#issuecomment-117288707 . I like it better that query parameters. Swagger UI renders them as different operations (useful) but shows the anchor tags. We have a local patch to hide them, as well as strip them on the actual API call or curl command. Again, this is only a workaround for 2.0; 3.0 addresses this better.

It looks like this has already been debated to death, but I wanted to weigh in here as well: I'm trying to retroactively build an OpenAPI specification for our API (Stripe), and this issue is the last blocker to getting one that passes validation. I'd reflexively written response schemas using oneOf to point to different responses and only later discovered that although they're valid JSON schema, they're not valid _OpenAPI JSON schema_.

My use cases are largely around endpoints that I refer to as "polymorphic". They group together a number of different resource types that are related and share a number of fields, but whose concrete implementations are different. An example might be to have an endpoint that lists payment sources, each of which could be a credit card, bank account, or other type.

I'll probably end up implementing one of the workarounds mentioned above (i.e. generating a schema for an artificial resource that has just the fields that are common to all types that could come back from an endpoint), but it would be nice to have a real solution so we could do better validation.

It seems that a number of arguments against allowing this revolve around "correctness" in that endpoint responses should always be totally deterministic. I'd suggest that although this is a somewhat noble goal, the OpenAPI community might be better served with a specification that's flexible enough to represent all types of APIs that might be found in the real world.

@brandur The current plan is for oneOf to be supported in OpenAPI 3.0 https://github.com/OAI/OpenAPI-Specification/pull/741

@brandur The current plan is for oneOf to be supported in OpenAPI 3.0 #741

Excellent news! :) Thanks for the quick response.

👍

Going over the tickets now, and it was definitely a nice read. I'm sure I managed to piss off some people with some of my replies, _but_ I'm very happy to have had this discussion and it helped us include a few additional features in 3.0.

Currently, the two relevant features that were added are:

  • support for anyOf and oneOf in the Schema Object.
  • support for multiple media type definitions for responses (see @darrelmiller's comments).

This covers a lot of what was requested here, but not necessarily all. However, I'm going to close the ticket for now and ask people to evaluate the existing options that were introduced to 3.0.0-RC0. If there's a strong need for more, I'd suggest opening a separate ticket for that so we can have a more focused discussion.

It's a matter of finding the balance between requirements, support, elegance and much more. The spec has evolved over the years and obviously supports today much more than it had initially.

My main concern with breaking away from the determinism principle is that it'll lead to changes all across the spec, and may increase its complexity substantially. This is not just an issue for the spec itself but also for the support tools. If we do it, we need to do it carefully and take it all under consideration.

The rules and standards are created to solve practical problem in better way. The need of the hour is to have specs to be flexible so that 1 API spec would be able to satisfy multiple response based on different client.

  • E.g. Profile API has 50 elements.
  • Client 1 will be provided by 5 elements
  • Client 2 will be provided by 10
    -Client 3 may get all 50

Going by current spec client 1 will see all the 50 elements as part of spec which we don't want them to see.

I have same problem.
The @OA annotation is case sensitivity,linux also case sensitivity,but windows case insensitivity,so must ensure capitalized is right.

Was this page helpful?
0 / 5 - 0 ratings