Skip to content

Kiota throws ApiException when server returns 304 #4190

Closed

Description

Our server returns HTTP 304 (Not Modified) without a response body when the incoming fingerprint in the If-None-Match header matches the stored version. This enables clients to poll for changes without receiving a response body, unless it has changed since it was last fetched.

Status 304 is described in the OAS document, but Kiota throws an ApiException saying the error isn't mapped:

Microsoft.Kiota.Abstractions.ApiException: The server returned an unexpected status code and no error factory is registered for this code: 304

OAS fragment:

{
  "paths": {
    "/api/people": {
      "get": {
        "tags": [
          "people"
        ],
        "summary": "Retrieves a collection of people.",
        "operationId": "getPersonCollection",
        "responses": {
          "200": {
            "description": "Successfully returns the found people, or an empty array if none were found.",
            "headers": {
              "ETag": {
                "description": "A fingerprint of the HTTP response, which can be used in an If-None-Match header to only fetch changes.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/vnd.api+json": {
                "schema": {
                  "$ref": "#/components/schemas/personCollectionResponseDocument"
                }
              }
            }
          },
          "400": {
            "description": "The query string is invalid.",
            "content": {
              "application/vnd.api+json": {
                "schema": {
                  "$ref": "#/components/schemas/errorResponseDocument"
                }
              }
            }
          },
          "304": {
            "description": "The fingerprint of the HTTP response matches one of the ETags from the incoming If_None_Match header.",
            "headers": {
              "ETag": {
                "description": "A fingerprint of the HTTP response, which can be used in an If-None-Match header to only fetch changes.",
                "required": true,
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    }
  }
}

Generated C# code:

namespace GeneratedClient.Api.People {
    public class PeopleRequestBuilder : BaseRequestBuilder {
        /// <summary>
        /// Retrieves a collection of people.
        /// </summary>
        /// <param name="cancellationToken">Cancellation token to use when cancelling requests</param>
        /// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
        public async Task<PersonCollectionResponseDocument?> GetAsync(Action<RequestConfiguration<PeopleRequestBuilderGetQueryParameters>>? requestConfiguration = default, CancellationToken cancellationToken = default) {
#nullable restore
#else
        public async Task<PersonCollectionResponseDocument> GetAsync(Action<RequestConfiguration<PeopleRequestBuilderGetQueryParameters>> requestConfiguration = default, CancellationToken cancellationToken = default) {
#endif
            var requestInfo = ToGetRequestInformation(requestConfiguration);
            var errorMapping = new Dictionary<string, ParsableFactory<IParsable>> {
                {"400", ErrorResponseDocument.CreateFromDiscriminatorValue},
            };
            return await RequestAdapter.SendAsync<PersonCollectionResponseDocument>(requestInfo, PersonCollectionResponseDocument.CreateFromDiscriminatorValue, errorMapping, cancellationToken).ConfigureAwait(false);
        }
    }
}

To work around this, we need to catch an exception to detect HTTP 304 is being returned:

var headerInspector = new HeadersInspectionHandlerOption
{
    InspectResponseHeaders = true
};

PersonCollectionResponseDocument? getResponse1 = await client.Api.People.GetAsync(
    configuration => configuration.Options.Add(headerInspector));

string eTag = headerInspector.ResponseHeaders["ETag"].Single();

try
{
    PersonCollectionResponseDocument? getResponse2 = await client.Api.People.GetAsync(
        configuration => configuration.Headers.Add("If-None-Match", eTag));
}
catch (ApiException exception) when (exception.ResponseStatusCode == (int)HttpStatusCode.NotModified)
{
    Console.WriteLine("The HTTP response hasn't changed, so no response body was returned.");
}

This looks like a bug to me. In KiotaBuilder.cs, I found the following line:

    private static readonly HashSet<string> noContentStatusCodes = new(StringComparer.OrdinalIgnoreCase) { "201", "202", "204", "205" };

Should that just be changed to include 304?

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

Metadata

Assignees

Labels

WIPenhancementNew feature or requestgeneratorIssues or improvements relater to generation capabilities.

Type

No type

Projects

  • Status

    Done ✔️

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions