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

Dynamic discriminator #3590

Closed
dotjpg3141 opened this issue Oct 26, 2023 · 6 comments
Closed

Dynamic discriminator #3590

dotjpg3141 opened this issue Oct 26, 2023 · 6 comments

Comments

@dotjpg3141
Copy link

I have an OData Endpoint that handles batch operations via /$batch. To differentiate between responses, the discriminator odata.metadata can be used. Unfortunately, this discriminator is tied to the API's base URL and varies depending on that URL. How can we handle a dynamic discriminator?

Schema

openapi: 3.0.0
info:
  title: OData Batch
  version: 1.0.0

paths:
  /v1.0/$batch:
    post:
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BatchRequest'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BatchResponse'

components:
  schemas:
    BatchRequest:
      type: object
      description: 'A JSON batch request body consists of a single JSON object with one required property: requests. https://learn.microsoft.com/en-us/graph/json-batching#request-format'
      properties:
        requests:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
              method:
                type: string
                enum:
                  - GET
                  - POST
                  - PATCH
                  - DELETE
              url:
                type: string
              body: # Proper schema definition left out for brevity
                type: object
                properties:
                  value:
                    type: object
                    additionalProperties: true
    BatchResponse:
        type: object
        description: 'The response format for JSON batch requests is similar to the request format. https://learn.microsoft.com/en-us/graph/json-batching#response-format'
        properties:
            responses:
            type: array
            items:
                type: object
                properties:
                id:
                    type: string
                status:
                    type: integer
                body:
                    oneof:
                     - $ref: '#/components/schemas/FooResponse'
                     - $ref: '#/components/schemas/BarResponse'
                    discriminator:
                      propertyName: odata.metadata # NOTE: This is the discriminator property
    
    FooResponse:
      type: object
      description: 'OData Variant Foo response'
      properties:
          odata.metadata:
              type: string
          value:
              type: array
              items: # Proper schema definition left out for brevity
                  type: object
                  additionalProperties: true
    
    BarResponse:
      type: object
      description: 'OData Variant Bar response'
      properties:
          d:
              type: object
              properties:
                  results:
                      type: array
                      items: # Proper schema definition left out for brevity
                          type: object
                          additionalProperties: true

Example Request

{
  "requests": [
    {
      "id": "1",
      "method": "GET",
      "url": "/foo",
    },
    {
      "id": "2",
      "method": "GET",
      "url": "/bar",
    }
  ]
}

Example Response

{
  "responses": [
    {
      "id": "1",
      "status": 200,
      "body": {
        "odata.metadata": "https://example.com/v1.0/$metadata#foo", // NOTE: This is the discriminator property
        "value": [
          {
            "id": "1",
            "name": "foo"
          }
        ]
      }
    },
    {
      "id": "2",
      "status": 200,
      "body": {
        "odata.metadata": "https://example.com/v1.0/$metadata#bar", // NOTE: This is the discriminator property
        "value": [
          {
            "id": "1",
            "name": "bar"
          }
        ]
      }
    }
  ]
}
@github-project-automation github-project-automation bot moved this to Todo in Kiota Oct 26, 2023
@baywet baywet self-assigned this Oct 26, 2023
@baywet baywet added this to the Backlog milestone Oct 26, 2023
@baywet
Copy link
Member

baywet commented Oct 26, 2023

Hi @dotjpg3141
Thanks for using kiota and for reaching out.
This should already be supported by kiota today:

  • The batch response will generate a union type wrapper
  • Deserialization to the right member type will be handled thanks to the discriminator mapping

What's missing from your description is the mapping property under the discriminator object as described in the specification.

Let us know if you have further questions.

@dotjpg3141
Copy link
Author

The value of the discriminator changes based on the base URL of the server.

If I generate the client based on a Swagger document on a.example.com, then this would be the schema:

body:
  oneof:
    - $ref: '#/components/schemas/FooResponse'
    - $ref: '#/components/schemas/BarResponse'
  discriminator:
    propertyName: odata.metadata
    mapping:
      https://a.example.com/v1.0/$metadata#foo: FooResponse
      https://a.example.com/v1.0/$metadata#bar: BarResponse

If I deploy this client to b.example.com, then the discriminator would change. Consequently, deserialization would fail, wouldn't it?

body:
  oneof:
    - $ref: '#/components/schemas/FooResponse'
    - $ref: '#/components/schemas/BarResponse'
  discriminator:
    propertyName: odata.metadata
    mapping:
      https://b.example.com/v1.0/$metadata#foo: FooResponse
      https://b.example.com/v1.0/$metadata#bar: BarResponse

@baywet
Copy link
Member

baywet commented Oct 27, 2023

Thanks for the additional information.
So is it essentially the same service deployed at different "places" ? Why would you have different odata metadata/type value then?
If you take the example of Microsoft Graph, which is deployed to a number of different clouds with different hostnames, the OData Type value doesn't vary based on the host. (in fact, only the fragment is advertised like #microsoft.graph.user)

@dotjpg3141
Copy link
Author

dotjpg3141 commented Oct 27, 2023

So is it essentially the same service deployed at different "places" ?

Exactly

(in fact, only the fragment is advertised like #microsoft.graph.user)

Interesting to know. I'll have to check the Microsoft OData examples. My current implementation returns an absolute path.

@baywet
Copy link
Member

baywet commented Oct 27, 2023

To answer your initial question, dynamic discriminators are not supported today. And I don't think we'll ever support them. This would require the factory of models to call into consumer provided code.
The alternative being to compare only the fragments (EndsWith, etc..) but that brings additional issues:

  • in the case the OpenAPI description is composed of multiple documents, and the schematized types might collide between the documents, the full discriminator would be required.
  • kiota emits switches for inheritance cases with discriminator, we'd have to replace that by a lot of if blocks, each of those comparing strings, which would be significantly slower.

Copy link
Contributor

This issue has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for 4 days. It will be closed if no further activity occurs within 3 days of this comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Archived in project
Development

No branches or pull requests

2 participants