Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for discriminator capability in response payloads #118

Closed
darrelmiller opened this issue Sep 17, 2021 · 16 comments · Fixed by #170
Closed

Add support for discriminator capability in response payloads #118

darrelmiller opened this issue Sep 17, 2021 · 16 comments · Fixed by #170
Assignees
Labels
type:enhancement Enhancement request targeting an existing experience.
Milestone

Comments

@darrelmiller
Copy link
Member

This would allow representation of heterogenous collections. Details to follow,

@darrelmiller darrelmiller self-assigned this Sep 17, 2021
@baywet baywet added the type:enhancement Enhancement request targeting an existing experience. label Nov 23, 2021
@baywet baywet added this to the 1.0.10 milestone Dec 17, 2021
@baywet
Copy link
Member

baywet commented Jan 4, 2022

@darrelmiller this is one of the last issues on the 1.0.10 roadmap, could you please provide the technical details so we can implement this as part of the conversion process?

@RabebOthmani
Copy link

Examples can be found here https://github.com/microsoft/kiota/pull/1014/files
@darrelmiller if you could please add more details on this tickets and some of the constraints we need to be paying attention to during the implementation. Thanks

@darrelmiller
Copy link
Member Author

The following example demonstrates how to define a discriminator property in a JSON Schema without needing to use the discriminator keyword. The property is defined as an enum with just a single allowed value. This is effectively the same as a const keyword that is supported in later versions of JSON Schema and OpenAPI 3.1.

The discriminator keyword is an OpenAPI invention and is not a standard JSON Schema keyword. Therefore it is better to define the discriminator behavior with native JSON Schema keywords.

The challenge for kiota is that it will need to identify that the type of object that needs to be instantiated is based on a property that has an enum constraint with a single value. We can solve with a Microsoft Graph core because we know what the discriminator is called, but to solve this generically, will require a little more creativity.

openapi: 3.0.3
info:
  title: Collection filtered by query parameter
  version: 1.0.0
servers:
  - url: https://example.org/
paths:
  /sessions:
    get: 
      parameters:
        - name: location
          in: query
          required: false
          schema:
            type: string
      responses:
        200:
          description: Ok
          content:
            application/json:
              schema:
                type: array
                item:
                  $ref: "#/components/schemas/session"
components:
  schemas:
    entity:
      type: object
      properties:
        id: 
          type: string
    session:
      allof:
        - $ref: "#/components/schemas/entity"
      type: object
      properties: 
        '@OData.Type': 
          type: string
          enum:
           - session
        displayName: 
          type: string
        location:
          type: string
    workshop:
      allof:
        - $ref: "#/components/schemas/session"
      type: object
      properties: 
        '@OData.Type': 
          type: string
          enum:
           - workshop
        requiredEquipment: 
          type: string
    presentation:
      allof:
        - $ref: "#/components/schemas/session"
      type: object
      properties: 
        '@OData.Type': 
          type: string
          enum:
           - presentation
        recorded: 
          type: boolean

Additionally, we should check for the DerivedTypeConstraint annotation to determine if we should present the response schema as a oneOf. e.g.

      responses:
        200:
          description: Ok
          content:
            application/json:
              schema:
                type: array
                item:
                  oneOf:
                    - $ref: "#/components/schemas/session"
                    - $ref: "#/components/schemas/workshop"
                    - $ref: "#/components/schemas/presentation"

@darrelmiller darrelmiller changed the title Add support for discriminator keyword in response payloads Add support for discriminator capability in response payloads Jan 24, 2022
@baywet
Copy link
Member

baywet commented Jan 24, 2022

so if I understand things property because the properties OData.Type are of type enum and because the enum values match a component name, they are discriminators? Also because there is inheritance between the components.

Shouldn't we wait for openapi.net to implement 3.1 so we can use the const keywork instead?

Also, as you mentioned, this description doesn't describe how to parse the discriminator from the API, would some x-ms extension on the document with some kind of templating be helpful?

e.g x-ms-discriminator-template: #microsoft.graph.{const}

@irvinesunday irvinesunday self-assigned this Jan 25, 2022
@darrelmiller
Copy link
Member Author

darrelmiller commented Jan 25, 2022

, they are discriminators?

They implicitly work as discriminators

Shouldn't we wait for openapi.net to implement 3.1 so we can use the const keywork instead?

We want Kiota to still work for OpenAPI 3.0. Most people have not migrated to 3.1 yet. Graph could use 3.1 once we support it.

this description doesn't describe how to parse the discriminator from the API,

I think we can infer the discriminator from the OpenAPI description. If we needed to be explicit, we can use the OpenAPI discriminator keyword.

@baywet
Copy link
Member

baywet commented Jan 26, 2022

Can you expand on why the example you provided doesn't include the discriminator keyword since it's part of 3.0?

@irvinesunday
Copy link
Collaborator

irvinesunday commented Jan 31, 2022

What's the difference between this approach and using the x-ms-discriminator-value extension we already use in the generated OpenAPI doc.?
e.g.:

image

@baywet
Copy link
Member

baywet commented Jan 31, 2022

with this extension, how do you know which field is the discriminator? (i.e. Odata.type)

@irvinesunday
Copy link
Collaborator

with this extension, how do you know which field is the discriminator? (i.e. Odata.type)

Actually that's what I'm scratching my head around too.
@darrelmiller ?

@darrelmiller
Copy link
Member Author

The x-ms-discriminator-value extension was intended to be used with the discriminator keyword in OpenAPI v2. It was needed because in V2 when you defined the discriminator the value in the payload had to match the schema name. In V3 we introduced a mapping property in the discriminator keyword which made the extension introduced by AutoREST redundant.

@darrelmiller
Copy link
Member Author

Following on from a conversation about this topic the current proposal is to continue using the discriminator keyword, as that is already implemented in OpenApi.Net.Odata. However, instead of using the x-ms-discriminator-value we should add support for using the mapping keyword that was introduced in OpenAPI v3.

@baywet
Copy link
Member

baywet commented Feb 2, 2022

@darrelmiller, can you provider a complete and updated example of what you want for v3 and v2 for clarity please?

@darrelmiller
Copy link
Member Author

This library only outputs V3, so I'm not sure why we care what V2 does.
From the OpenAPI specification, this is how discriminators are described.

MyResponseType:
  oneOf:
  - $ref: '#/components/schemas/Cat'
  - $ref: '#/components/schemas/Dog'
  - $ref: '#/components/schemas/Lizard'
  - $ref: 'https://gigantic-server.com/schemas/Monster/schema.json'
  discriminator:
    propertyName: petType
    mapping:
      dog: '#/components/schemas/Dog'
      monster: 'https://gigantic-server.com/schemas/Monster/schema.json'

https://spec.openapis.org/oas/v3.1.0#fixed-fields-20

@baywet
Copy link
Member

baywet commented Feb 3, 2022

So to translate that in an example closer to Microsoft Graph, it'd look like this for /groups/{id}/members

DirectoryObjectResponseType:
  oneOf:
  - $ref: '#/components/schemas/microsoft.graph.servicePrincipal'
  - $ref: '#/components/schemas/microsoft.graph.user'
  - $ref: '#/components/schemas/microsoft.graph.device'
  - $ref: '#/components/schemas/microsoft.graph.orgContact'
  discriminator:
    propertyName: @odata.type
    mapping:
      '#microsoft.graph.servicePrincipal': '#/components/schemas/microsoft.graph.servicePrincipal'
      '#microsoft.graph.user': '#/components/schemas/microsoft.graph.user'
      '#microsoft.graph.device': '#/components/schemas/microsoft.graph.device'
      '#microsoft.graph.orgContact': '#/components/schemas/microsoft.graph.orgContact'

Correct?

@irvinesunday
Copy link
Collaborator

Or perhaps something like this?

microsoft.graph.directoryObject:
    allOf:
    - $ref: '#/components/schemas/microsoft.graph.entity'
    - title: directoryObject
        type: object
        properties:
        deletedDateTime:
            pattern: '^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$'
            type: string
            format: date-time
            nullable: true
        discriminator:
        propertyName: '@odata.type'
        mapping:
            directoryObject: '#microsoft.graph.directoryObject'

@baywet
Copy link
Member

baywet commented Feb 4, 2022

I think you're correct for the most part except the mapping key should be the odata type value and the mapping value should be the component full key.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type:enhancement Enhancement request targeting an existing experience.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants