Skip to content

Different error result classes depending on content types are not supported #7338

@WolfgangHG

Description

@WolfgangHG

What are you generating using Kiota, clients or plugins?

API Client/SDK

In what context or format are you using Kiota?

Windows executable

Client library/SDK language

Csharp

Describe the bug

The X openapi specification describes the results for the "delete a tweet" endpoint like this:

    "/2/tweets/{id}" : {
      "delete" : {
        ...
        "summary" : "Delete Post",
        "description" : "Deletes a specific Post by its ID, if owned by the authenticated user.",
        "externalDocs" : {
          "url" : "https://developer.twitter.com/en/docs/twitter-api/tweets/manage-tweets/api-reference/delete-tweets-id"
        },
        "operationId" : "deletePosts",
        "parameters" : [
          {
            "name" : "id",
            "in" : "path",
            "description" : "The ID of the Post to be deleted.",
            "required" : true,
            "schema" : {
              "$ref" : "#/components/schemas/TweetId"
            },
            "style" : "simple"
          }
        ],
        "responses" : {
          "200" : {
            "description" : "The request has succeeded.",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/TweetDeleteResponse"
                }
              }
            }
          },
          "default" : {
            "description" : "The request has failed.",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/Error"
                }
              },
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },

Note that the non-success result object depends on the content type of the response: for "application/json" it should be an "Error" class, for "application/problem+json" it should be a "Problem" class.

If I delete a random ID (that I am not allowed to delete), the raw response is:

{
  "detail": "You are not authorized to delete this Tweet.",
  "type": "about:blank",
  "title": "Forbidden",
  "status": 403
}

(found by extracting the response content with a BodyInspectionHandlerOption)

Those are the properties of the "Problem" class:

      "Problem" : {
        "type" : "object",
        "description" : "An HTTP Problem Details object, as defined in IETF RFC 7807 (https://tools.ietf.org/html/rfc7807).",
        "required" : [
          "type",
          "title"
        ],
        "properties" : {
          "detail" : {
            "type" : "string"
          },
          "status" : {
            "type" : "integer"
          },
          "title" : {
            "type" : "string"
          },
          "type" : {
            "type" : "string"
          }
        },

But in my code, I can catch only a meaningless exception, and you can see that it is an "Error" class without any details:

XbyOpenApi.Core.Client.Models.Error
  HResult=0x80131500
  Message=
  Source=Microsoft.Kiota.Http.HttpClientLibrary
  StackTrace:
   at Microsoft.Kiota.Http.HttpClientLibrary.HttpClientRequestAdapter.<ThrowIfFailedResponseAsync>d__30.MoveNext()
   at Microsoft.Kiota.Http.HttpClientLibrary.HttpClientRequestAdapter.<SendAsync>d__22`1.MoveNext()
   at Microsoft.Kiota.Http.HttpClientLibrary.HttpClientRequestAdapter.<SendAsync>d__22`1.MoveNext()
   at XbyOpenApi.Core.Client.Two.Tweets.Item.ItemRequestBuilder.<DeleteAsync>d__12.MoveNext() in E:\Projekte\Github\XbyOpenApi\XbyOpenApi.Core\Client\Two\Tweets\Item\ItemRequestBuilder.cs:line 87

  This exception was originally thrown at this call stack:
    Microsoft.Kiota.Http.HttpClientLibrary.HttpClientRequestAdapter.ThrowIfFailedResponseAsync(System.Net.Http.HttpResponseMessage, System.Collections.Generic.Dictionary<string, Microsoft.Kiota.Abstractions.Serialization.ParsableFactory<Microsoft.Kiota.Abstractions.Serialization.IParsable>>, System.Diagnostics.Activity, System.Threading.CancellationToken)
    Microsoft.Kiota.Http.HttpClientLibrary.HttpClientRequestAdapter.SendAsync<ModelType>(Microsoft.Kiota.Abstractions.RequestInformation, Microsoft.Kiota.Abstractions.Serialization.ParsableFactory<ModelType>, System.Collections.Generic.Dictionary<string, Microsoft.Kiota.Abstractions.Serialization.ParsableFactory<Microsoft.Kiota.Abstractions.Serialization.IParsable>>, System.Threading.CancellationToken)
    Microsoft.Kiota.Http.HttpClientLibrary.HttpClientRequestAdapter.SendAsync<ModelType>(Microsoft.Kiota.Abstractions.RequestInformation, Microsoft.Kiota.Abstractions.Serialization.ParsableFactory<ModelType>, System.Collections.Generic.Dictionary<string, Microsoft.Kiota.Abstractions.Serialization.ParsableFactory<Microsoft.Kiota.Abstractions.Serialization.IParsable>>, System.Threading.CancellationToken)
    XbyOpenApi.Core.Client.Two.Tweets.Item.ItemRequestBuilder.DeleteAsync(System.Action<Microsoft.Kiota.Abstractions.RequestConfiguration<Microsoft.Kiota.Abstractions.DefaultQueryParameters>>, System.Threading.CancellationToken) in ItemRequestBuilder.cs

So the result is mapped to the wrong class.

This error is thrown from HttpClientRequestAdapter.ThrowIfFailedResponseAsync where only a status code mapping is done.
I don't know whether a content type mapping should also happen there or in the generated client code.

In the generated client code, the method looks like this: only the class "Error" is handled:

    public async Task<global::XbyOpenApi.Core.Client.Models.TweetDeleteResponse> DeleteAsync(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default, CancellationToken cancellationToken = default)
    {
      var requestInfo = ToDeleteRequestInformation(requestConfiguration);
      var errorMapping = new Dictionary<string, ParsableFactory<IParsable>>
            {
                { "XXX", global::XbyOpenApi.Core.Client.Models.Error.CreateFromDiscriminatorValue },
            };
      return await RequestAdapter.SendAsync<global::XbyOpenApi.Core.Client.Models.TweetDeleteResponse>(requestInfo, global::XbyOpenApi.Core.Client.Models.TweetDeleteResponse.CreateFromDiscriminatorValue, errorMapping, cancellationToken).ConfigureAwait(false);
    }

I think this is actually a bug on the X side, because the doc at https://docs.x.com/x-api/posts/delete-post and the OpenAPI description don't match - according to the doc it should even be an array of "Problems".

I even managed to create a reponse like this that does not appear in the api description:

{
    "errors": [
        {
            "parameters": {
                "id": [
                    "1261326399320715264,1278347468690915330"
                ]
            },
            "message": "The `id` query parameter value [1261326399320715264,1278347468690915330] is not valid"
        }
    ],
    "title": "Invalid Request",
    "detail": "One or more parameters to your request was invalid.",
    "type": "https://api.twitter.com/2/problems/invalid-request"
}

Expected behavior

What do you think? Would it be possible in the Kiota generated client to create a response discriminator based on the content type?

I think this is the RFC that defines this behavior: https://datatracker.ietf.org/doc/html/rfc7807

Unfortunately, even if you handle this perfectly, it will not work because of X "problems".

How to reproduce

Sorry, as you need a (free) X account, you cannot reproduce this probably

Open API description file

https://api.twitter.com/2/openapi.json

Kiota Version

1.30.0

Latest Kiota version known to work for scenario above?(Not required)

No response

Known Workarounds

No response

Configuration

No response

Debug output

Click to expand log ```
</details>


### Other information

_No response_

Metadata

Metadata

Assignees

No one assigned

    Labels

    CsharpPull requests that update .net codestatus:waiting-for-triageAn issue that is yet to be reviewed or assignedtype:bugA broken experience

    Type

    No type

    Projects

    Status

    Needs Triage 🔍

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions