Ask a Question

OpenAPI Discrimintors

silverbullet2
New Contributor

OpenAPI Discrimintors

I have been trying to figure out how to handle a situation where a query string parameter changes the response object.  Here's my example:
The default response is this:
endpoint: http://myapi.com/pets

response: 

 

{
pet:{
name: "barry",
color: "white",
age: 17
}
}

 

 

It can also have a query string parameter where it adjusts the response as follows:

endpoint: http://myapi.com/pets?attributes=name,color

response: 

 

 

{
pet:{
name: "barry"
}
}

 

 

 

It also supports comma separated lists for the parameter as well like this:

endpoint: http://myapi.com/pets

response: 

 

 

{
pet:{
name: "barry",
color: "white"
}
}

 

 

 

Is there a way to describe this scenario using discriminators?  I know that I can list the responses as optional, but that wasn't ideal.

 

Thanks!

5 REPLIES 5

Hi @silverbullet2,

 

I’m not 100% sure what you are trying to achieve in your example.

 

Are you trying to allow the consumer specify the properties that they are interested in and only return those properties?

`http://myapi.com/pets` --> return all pets according to the defined response schema

`http://myapi.com/pets?attributes=name,color` --> return all pets and only return the `name` and `color` property for each pet

 

Or are you trying to return two or more different types of objects (aka polymorphism)? If so then you would use `oneOf` or `anyOf` to describe those schemas. If so, then you may want to use a discriminator. You can find some examples of that at https://swagger.io/specification/#discriminator-object 

 

Or are you looking for inheritance by defining a base schema, and then inherit from it using `allOf`? You can also use a discriminator if needed.

 

An rough example of the inheritance with a discriminator could be to have a base "Insurance" schema and then to extend the insurance schema using `allOf`:

 

type: object
description: Insurance
discriminator:
  propertyName: insuranceType
  mapping:
    house: '#/components/schemas/HouseInsurance'
    car: '#/components/schemas/CarInsurance'
    travel: '#/components/schemas/TravelInsurance'
properties:
  policyNumber:
    description: The insurance policy number
    type: string
    example: IXP12345678
  startAt:
    type: string
    format: date-time
    pattern: '^[0-9]{4}-[0-9]{2}-[0-9]{2}T[012][0-9]:[0-5][0-9]:[0-5][0-9]Z$'
    description: The date-time from which this policy becomes active
  endAt:
    type: string
    format: date-time
    pattern: '^[0-9]{4}-[0-9]{2}-[0-9]{2}T[012][0-9]:[0-5][0-9]:[0-5][0-9]Z$'
    description: The date-time from which this policy ceases

 

 

 

allOf:
  - $ref: ./Insurance.yaml
  - type: object
    description: Car Insurance
    properties:
      make:
        description: The make of care
        type: string
        example: Nissan
      range:
        description: car model range
        type: string
        example: hatchback
      model:
        description: car model
        type: string
        example: micra
      registrationNumber:
        description: the registration number of the vehicle   
        type: string
        example: 00G1011     
      value:
        type: number
        format: double
        description: The value of the vehicle
        example: 2000.0 

 

 

In general, discriminators can add complexity to the design and they must be used in conjunction with `anyOf`, `oneOf`, `allOf`. The discriminator must apply to the same level of the schema it is declared.

 

@frankkilcommins, I am trying to implement the first option you gave.  I want to allow the consumer to specify the properties they are interested in and only return those properties.  Is there a way to utilize discriminators for that or what would be the best way to accomplish this?

Hi @silverbullet2 

 

Discriminators would generally not be used where you want to allow the consumer describe the shape of the response representation. Think of them more as a shortcut to possibly allow a consumer make a choice from a selection of possible pre-defined schemas. This does not really fit into the example you describe as you just want to reduce the representation of a single schema.

 

There is no definitive right way to achieve your goal, but a common technique is to have a query parameter (named something like ‘fields’ or ‘properties’ or ‘attributes’ or ‘select’) to allow a consumer specify a subset of properties they would like to be returned with a successful response, rather than the full representation of the resource.

 

It’s up to you as the API provider to decide what query parameter naming to choose, and to decide if you limit to root level properties or also enable the consumer to select subset of properties within nested objects or arrays that may be returned in the representation. If you’re going to allow selection to subsets within nested objects or arrays, then it would be common to apply a sub-selector expression (generally this would be based on XPath syntax or object.member dot notation)

 

Some examples:

http://someapiurl.com?fields=color,name –> return all pets and only the color and name properties

http://someapiurl.com?fields=color,name,location/city OR http://someapiurl.com?fields=color,name,location.city –> return all pets and only the color and name root level properties as well as the city property from the location object.

http://someapiurl.com?fields=color,name,hobbies(id, name) –> return all pets and only return the color, name properties, and just the id and name properties for items in the hobbies array

 

It’s also relatively common that providers leverage bits and pieces from specifications like OData, SCIM or syntaxes like Lucene for advanced filtering/querying (these approaches can increase complexity however).

 

Things to keep in mind:

  • URLs have limits so work to ensure that consumers can not hit URL limits (good practice is to ensure that any combination of params will not result in length greater than ~2000 characters)
  • You will need to validate request to ensure that the selector parameter is valid and return the appropriate 400 series HTTP Status code if it contains an error or is otherwise invalid

 

If you wanted to go full query orientated, then you could consider GraphQL which has it’s own pro’s an con’s.

 

In general, I would always promote good design where the minimal representation to address the business goal is returned. Separate your APIs into separate resources (or sub-resources), giving your consumer the ability to make more concise calls. Progressively enhance the consumer experience with options to expand or embed additional resources when appropriate.

 

Ensure you leverage HTTP caching when/where possible to ensure that consumers can cache representations while its safe to do so.

 

I hope this helps you achieve what you need to do with your API.

We are already doing as you describe and only allow selection within the root level, not any of the nested arrays.  The issue I am trying to resolve is how can I write an OpenAPI spec that would describe these possibilities and support validating the output?  It sounds like discriminators is not the proper way to describe this, but what options are available to accomplish this?  Could you provide an example using the requests/responses I provided in the original post?

Hi @silverbullet2 

 

Much depends how opinionated you would like to be in the design and what your needs are for extensibility. Here are some options that you may find helpful.

 

The most common mechanism would be to define a query parameter which accepts an array of strings and to use the description and examples to provide appropriate guidance for the API consumer.

 

An example of that would be:

      parameters:
        - in: query
          name: fields
          description: You can use the _fields_ parameter to restrict the set of properties returned to only those you explicitly specify. The properties are specified as a comma-separated list. For example, to return only the `name` and `color` properties of pets, you would set the parameter like `fields=name,color`          
          schema:
            type: array
            uniqueItems: true
            items:
              type: string
          style: form
          explode: false   
          examples:
            singleProperty:
              summary: Return only the name of the Pet
              value: [name] 
            multipleProperties:
              summary: Return the Pet's name, age and color
              value: [name, age, color]

 

You can also define the parameter as an array of string enums. Enums are generally regarded as complete (e.g. you would not extend overtime in case of breaking a consumer implementation). However as this would only ever be for a request parameter and assuming you follow the practice of never removing a property from your design (e.g. breaking change), then it could fit your needs. This option gives you the ability to assert the condition against the specification itself.

      parameters:
        - in: query
          name: fields
          description: You can use the _fields_ parameter to restrict the set of properties returned to only those you explicitly specify. The properties are specified as a comma-separated list. For example, to return only the `name` and `color` properties of pets, you would set the parameter like `fields=name,color`          
          schema:
            type: array
            uniqueItems: true
            items:
              type: string
          style: form
          explode: false   
          examples:
            singleProperty:
              summary: Return only the name of the Pet
              value: [name] 
            multipleProperties:
              summary: Return the Pet's name, age and color
              value: [name, age, color]

 

You could also specify an enum as part of an anyOf to guide the consumer but leave it open for further extension (not as strict as the previous option).

      parameters:
        - name: attributes
          in: query
          schema:
            type: array
            items:
              type: string
              anyOf:
                - enum: [name, color, age]
                - {}  
          required: false
          description: You can use the _attributes_ parameter to restrict the set of properties returned to only those you explicitly specify. The properties are specified as a comma-separated list. For example, to return only the `name` and `color` properties of pets, you would set the parameter like `fields=name,color`. The known values are defined by the following enumeration [`name `, `age`, `color`] but this may extend overtime and other values provided will be processed if possible
          style: form
          explode: false
          examples:
            singleProperty:
              summary: Return only the name of the Pet
              value: [name] 
            multipleProperties:
              summary: Return the Pet's name, age and color
              value: [name, age, color]

Regardless of the chosen option, you would have to implement the logic to parse the query parameter and use the values to construct a response representation based on the provided selections.

 

I'd recommend providing some examples in your responses to demonstrate a full response representation as well as some filtered examples.

 

Example

      responses:
        '200':
          description: Get Pets response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Pets'  
              examples:
                default:
                  value:
                      - name: spot
                        color: black
                        age: 2
                        eyeColor: brown
                nameOnly - only name provided in query:
                  value:
                      - name: spot
                nameAndAge - two propertiese provided in query:
                  value:
                      - name: spot
                        age: 2

 

I hope this helps you make a choice for your implementation.

 

cancel
Showing results for 
Search instead for 
Did you mean: