OAI / OpenAPI-Specification

The OpenAPI Specification Repository
https://openapis.org
Apache License 2.0
28.83k stars 9.07k forks source link

Defining constant value in response #1313

Closed halmai closed 4 years ago

halmai commented 7 years ago

In order to specify that the response for an API call is always a certain given value, I would like to create this this feature request.

If the client wants to get the details of a non-existing pet of a pet store then the server should say

{
    "status": "ERROR",
    "error_message": "ERROR__PET_NOT_FOUND"
}

The best way I found for describing this is to use enums with only one element, like this:

    schema:
        type: object
        properties:
            result: 
                type: string
                enum: [ERROR]
            error_code: 
                type: string
                enum: [ERROR__PET_NOT_FOUND]

Instead of this, an exact value should be defined with an implicit type detection. So,

    schema:
        type: object
        properties:
            result: 
                value: "ERROR"
            error_code: 
                value: "ERROR__PET_NOT_FOUND"

should mean the same as the previous declaration. The following scalar types should be auto-detected:

Moreover, the new value declaration should work one level above as well:

    schema:
        type: object
        properties:
            value: 
                result: "ERROR"
                error_code: "ERROR__PET_NOT_FOUND"

This specification should mean a structure that has the two fields with these two constant values.

One more step would be the following notation:

    schema:
        value: 
            result: "ERROR"
            error_code: "ERROR__PET_NOT_FOUND"

This would determine the type to be an objects and the properties as above.

This notation would be much more dense and easier to both write and understand.

darrelmiller commented 7 years ago

If an API had an exact response then it would seem rather redundant to call it. Why even have an API if it returns the same value every time?

halmai commented 7 years ago

The above example shows a response which is returned only if the targeted element doesn't exist, that is, with http status code = 404. In other cases it replies something different. The different kinds of errors can produce different responses.

In the project I am working in, the JSON response always has a key "status" which can be either "OK" or "ERROR" and in case of "ERROR" there must exist an "error_code" key as well (and potentially others).

The fact that the response contains the error code as well, it makes the client development easier because the client doesn't have to interpret all the http status codes.

In case of 404 of a particular API endpoint the value of the error_code is always the same but in case of 200 it is varying.

darrelmiller commented 7 years ago

@halmai Taking my OAI hat off for the moment, because this is purely a personal opinion. Also, this comment isn't directed specifically at you, it is just that your assertion reflects a common belief that I feel needs to be addressed.

You said,

it makes the client development easier because the client doesn't have to interpret all the http status codes

I believe that this is incorrect. Moving the error description into the response body "for consistency" does help map HTTP APIs to a client side RPC signature. It allows HTTP APIs to be presented in a way that client developers are more familiar with. However, it is definitely not easier than using the HTTP status codes as they were intended to be used.

In your example, you already know that the response is a 404. Having a client understand that HTTP Status code 404 means "not found" is not only simple, it is consistent across all HTTP API implementations. Requiring a client developer to have to read a payload of some media type, that has some custom error payload structure, that then has some custom enum that repeats the error "not found" is not easier.

Client libraries do not have to interpret ALL of the HTTP status codes. It needs to understand 200, 300, 400, and 500 plus optionally any extra codes that the client wants to do special handling for. The HTTP spec says that if you receive a status code that you don't understand, round it down to the nearest hundred and treat it as that.

It can be useful to have payloads for response bodies that have additional details. However, there is a standard for that https://tools.ietf.org/html/rfc7807 which has existing implementations. These things should not be reinvented over and over again.

Finally, your original request was the ability to define a schema for a constant value. OpenAPI bases it's schema on JSON Schema, and JSON schema doesn't support what you are trying to do. If you want JSON Schema to change, then I would suggest talking to them, we don't have that authority.

fcanela commented 7 years ago

That practice is very common. I am facing the same problem than the original user. Being able to model that constant property would enable us to property document an existing API and feed the API Manager with it accordingly.

webron commented 7 years ago

@fcanela you do have a to model a constant value. What @halmai suggested was syntactic sugar.

andig commented 7 years ago

@darrelmiller I think I have a similar use case. My API returns its version number in the payload which is currently fixed ("0.4"). As the application is distributed as bundle it does not make much sense to version that API by path.

tobymurray commented 6 years ago

OpenAPI bases it's schema on JSON Schema, and JSON schema doesn't support what you are trying to do. If you want JSON Schema to change, then I would suggest talking to them, we don't have that authority.

I've just started using OpenAPI, so I may be lacking a lot of context, but is this json-schema-org ticket an indication that the work on the JSON Schema side has already been done? I.e. the released Pre-preview 2017-03-09 already contains this value, which is visible in the source here.

So is this functionality now blocked by an official release or something of JSON Schema? Is it something OpenAPI can adopt while still in draft?

Specifically:

6.24. const

The value of this keyword MAY be of any type, including null.

An instance validates successfully against this keyword if its value is equal to the value of the keyword

handrews commented 6 years ago

@darrelmiller @tobymurray @halmai JSON Schema introduced const in draft-06 (technically draft-wright-json-schema-validation-01). I believe OpenAPI 3.0 references draft-wright-json-schema-validation-00. We are about to release another draft (draft-07, exact IETF naming TBD) within the next month or two.

The implementation delta between draft-06 and draft-07 is smaller than that from older drafts to draft-06. And some of it (e.g. moving readOnly from hyper-schema into validation, supporting additional common format values) just reflects what many people already do anyway.

So at this point it depends on if/when OpenAPI chooses to move to a newer draft. There are now draft-06 implementations in the wild in JavaScript, Java, Go, and .NET (and hopefully soon Python).

darrelmiller commented 6 years ago

We will review updating our support for newer versions of JSON Schema when we are considering the features that will be going into 3.1.

andy-maier commented 6 years ago

I'd like to register my interest in what @halmai originally requested. My use case is that in our responses there are some generic properties for making the response a bit more self-describing. For example, every response has a "kind" property that allows identifying what kind of stuff it contains.

We currently follow what @halmai found, namely to define an enum with one value. For example, the definition of an error response states that "kind" must be "error" as follows:

components:
  responses:
    error:
      content:
        application/json:
          schema:
            properties:
              kind:
                type: string
                enum: [error]
              ... (more properties) ...

In the Swagger 3.x editor, the Model view of the response shows that property as:

Enum:
   > Array [ 1 ]

When opening the > twistie, the enumeration shows its one value:

Enum:
   v [ error ]

That is quite inconvenient, and a const definition for such a fixed value would allow the editor to show the value right away.

Plus, stating a required fixed value via a construct such as "const" is semantically really much more to the point of what the interface wants to express in such a case., compared to defining an enum with one value.

I'm looking forward to see OpenAPI 3.1 embrace this feature from the JSON schema.

webron commented 6 years ago

@andy-maier specifically what you're describing is more of a tooling issue than a spec issue.

andy-maier commented 6 years ago

I have meanwhile found the same behavior also in the Python client generated by the Swagger 2.0 tooling, and have come to the conclusion that the tooling has no chance (!!) to convert an enum with one value into a const.

The reason for that is that it is possible that the enum list becomes longer in a future version of the API YAML file, so the tooling has to present that faithfully as specified, in order not to cause an incompatible change for its users in the future (e.g. a Python program using the API provided by the generated Python client module).

As long as the API YAML file does not distinguish between an enum with one value (that might become more than one value in the future) and a fixed value, the tooling must behave that way.

So the spec format is the root cause for the behavior of the tooling, and that is not just a flaw in the tooling.

handrews commented 6 years ago

@andy-maier that's a really interesting point that I didn't even think of when we added const to JSON Schema! (btw, draft-07 is out!)

phal0r commented 6 years ago

I agree with @andy-maier: We have the same use case with the difference, that we call it type not kind for each entity ;)

We took the same approach with using enum with one element. During the time, there were misinterpretations, where developer would mistakenly assume, that this property is an array, while using the swagger ui.

jmini commented 6 years ago

We are interested by OpenAPI supporting the notion of a constant in the schema definition.

For legacy reasons on the backend, the current API I am working on requires in each object a constant to be defined. Its more of less represent the precise type of the object and helps the server to perform the deserialisation correctly.

This means that each JSON Object sent by the client needs to set the information, which is the same for all objects corresponding to a specific schema. Example:

{
  "$_type": "User",
  "id": 132,
  "firstName": “John”,
  "lastName": “Doe”
}

Right now we are using the enum approach described in this issue:

components:
  schemas:
    User:
      type: object
      properties:
        $_type:
          type: string
          enum:
            - User
        id:
          type: integer
        firstName:
          type: string
        lastName:
          type: string

It looks well in the documentation (swagger online editor):

Documentation screenshot

In the client (java code in this example), need explicitly to set the type each time an object is instantiated:

User u = new User();
u.setType(TypeEnum.USER);
u.setId(132);
u.setFirstName("John");
u.setLastName("Doe");

With a constant, I assume that the line u.setType(..) would not be necessary to generate the same JSON Payload.

jmini commented 6 years ago

Having a constant could be interesting for the Inheritance and Polymorphism case (when a discriminator and a mapping are defined in a oneOf schema). Take this example from the guide:

components:
  responses:
    sampleObjectResponse:
      content:
        application/json:
          schema:
            oneOf:
              - $ref: '#/components/schemas/Object1'
              - $ref: '#/components/schemas/Object2'
            discriminator:
              propertyName: objectType
              mapping:
                obj1: '#/components/schemas/Object1'
        obj2: '#/components/schemas/Object2'
  …
  schemas:
    Object1:
      type: object
      required:
        - objectType
      properties:
        objectType:
          type: string
      …
    Object2:
      type: object
      required:
        - objectType
      properties:
        objectType:
          type: string
      …

In this case, objectType defined in #/components/schemas/Object1 and in #/components/schemas/Object2 is just present for the discriminator mapping, it would be much nicer to have it defined as constant.

jnovotny commented 6 years ago

I'd love to see this implemented as I'm having difficulty with geojson_swagger

For Point: http://geojson.org/geojson-spec.html#id2
For Polygon: http://geojson.org/geojson-spec.html#id4

Please see my original issue at https://github.com/swagger-api/swagger-codegen/issues/7756#issuecomment-370137779

handrews commented 6 years ago

@jmini on the JSON Schema side, we're hoping to add a code generation vocabulary with the OpenAPI community being one of the main drivers. This would provide a standardized way to disambiguate JSON Schema validation constructs that were never intended to be used without an instance. e.g. an allOf might be used for inheritance or composition, but there's no clear way to indicate that.

I'm rather hoping that discriminator won't be needed after that (it breaks some assumptions in JSON Schema or else we'd try to adopt it in the vocabulary).

We definitely have found in draft-06 schemas that const makes many oneOf use cases much more clear. In draft-07, const + if makes certain cases even more clear, although not always concise.

gvdmarck commented 5 years ago

Is there any update on this ? If you want a clear use-case, just think json:api :

GET /articles/

response:

{
  "data": [
    {
    "type": "articles",
    "id": "1",
    ...
   },
   {
    "type": "articles",
    "id": "2",
    ...
   },
   {
    "type": "articles",
    "id": "3",
    ...
   }, ...]
}

The current workaround (enum with one value) is so hacky I don't understand how it is sufficient/reasonable for everyone.

handrews commented 5 years ago

@gvdmarck JSON Schema 2019-09 (formerly known as draft-08) has now been published.

PR #1977 in this repository updates OpenAPI's Schema Object for OAS 3.1 to use JSON Schema 2019-09, which includes the const keyword (added back in draft-06). Assuming that PR is eventually accepted, it will solve this problem in OAS 3.1.

ffMathy commented 4 years ago

Any progress on this? I'm personally interested in it from the polymorphism perspective @jmini pointed out.

It's very powerful in TypeScript, since that supports constant string values, and can use it as descriminators.

philsturgeon commented 4 years ago

This was fixed in #1977 so we can close this.

seyedmmousavi commented 3 months ago

@halmai Taking my OAI hat off for the moment, because this is purely a personal opinion. Also, this comment isn't directed specifically at you, it is just that your assertion reflects a common belief that I feel needs to be addressed.

You said,

it makes the client development easier because the client doesn't have to interpret all the http status codes

I believe that this is incorrect. Moving the error description into the response body "for consistency" does help map HTTP APIs to a client side RPC signature. It allows HTTP APIs to be presented in a way that client developers are more familiar with. However, it is definitely not easier than using the HTTP status codes as they were intended to be used.

In your example, you already know that the response is a 404. Having a client understand that HTTP Status code 404 means "not found" is not only simple, it is consistent across all HTTP API implementations. Requiring a client developer to have to read a payload of some media type, that has some custom error payload structure, that then has some custom enum that repeats the error "not found" is not easier.

Client libraries do not have to interpret ALL of the HTTP status codes. It needs to understand 200, 300, 400, and 500 plus optionally any extra codes that the client wants to do special handling for. The HTTP spec says that if you receive a status code that you don't understand, round it down to the nearest hundred and treat it as that.

It can be useful to have payloads for response bodies that have additional details. However, there is a standard for that https://tools.ietf.org/html/rfc7807 which has existing implementations. These things should not be reinvented over and over again.

Finally, your original request was the ability to define a schema for a constant value. OpenAPI bases it's schema on JSON Schema, and JSON schema doesn't support what you are trying to do. If you want JSON Schema to change, then I would suggest talking to them, we don't have that authority.

But this approach doesn't seem to be right. Just take a look at https://softwareengineering.stackexchange.com/a/305294/244734
Getting tied to the underlying technology doesn't seem like a good idea.

uniqueg commented 3 months ago

Did anyone ever use const in a response in OAI 3.1? How does it work? Is there any documentation available? :pray:

handrews commented 3 months ago

@uniqueg const is a standard JSON Schema keyword so you can see its documentation in JSON Schema draft 2020-12 (which is linked from the OAS 3.1 specification).

It works just like two of the proposed solutions in the original comment, but with const in place of value:

You can use it for individual properties:

    schema:
        type: object
        properties:
            result: 
                const: "ERROR"
            error_code: 
                const: "ERROR__PET_NOT_FOUND"

Or as the entire schema:

    schema:
        const: 
            result: "ERROR"
            error_code: "ERROR__PET_NOT_FOUND"

There was another syntax proposed in the initial comment that put value directly under properties - that is not supported because keywords can only appear in schema objects, and the immediate value of properties is an object of schemas, not a schema itself.

uniqueg commented 3 months ago

Thank you so much for taking the time to post that example - this is awesome! :rocket:

Let's see if this is also supported by our tooling :upside_down_face: