Skip to content

Error response without a body: message ends in <class 'NoneType'> and the get_object_value span is never ended #755

Description

When an error status has an error class registered but the response has no body (a 503 from a gateway, with no Content-Type), get_root_parse_node returns None (httpx_request_adapter.py#L420), _get_error_from_response returns None (#L493), and throw_failed_responses puts type(error) into the message (#L578). So the message ends in <class 'NoneType'>. kiota-dotnet builds the same message with the status code (HttpClientRequestAdapter.cs#L592), and the two "no error class is registered" messages in the same function already end in it (#L528, #L544).

In the same function, the get_object_value span is started before the None check (#L491), and .end() runs only after get_object_value returns (#L498). It is never ended when there is no body, and also not when get_object_value raises (the path #713 added, #L561).

Repro:

import asyncio
from importlib.metadata import version

import httpx
from kiota_abstractions.api_error import APIError
from kiota_abstractions.authentication import AnonymousAuthenticationProvider
from kiota_abstractions.method import Method
from kiota_abstractions.request_information import RequestInformation
from kiota_http.httpx_request_adapter import HttpxRequestAdapter
from opentelemetry import trace
from opentelemetry.sdk.trace import SpanProcessor, TracerProvider

started = []


class RecordStarts(SpanProcessor):
    def on_start(self, span, parent_context=None):
        started.append(span)


class ExampleError(APIError):
    @staticmethod
    def create_from_discriminator_value(parse_node):
        return ExampleError()

    def get_field_deserializers(self):
        return {}

    def serialize(self, writer):
        pass


async def main():
    provider = TracerProvider()
    provider.add_span_processor(RecordStarts())
    trace.set_tracer_provider(provider)
    print(f"kiota-http {version('microsoft-kiota-http')}")

    # a 503 from a gateway, with no body and no Content-Type
    transport = httpx.MockTransport(lambda request: httpx.Response(503))
    async with httpx.AsyncClient(transport=transport) as client:
        adapter = HttpxRequestAdapter(AnonymousAuthenticationProvider(), http_client=client)
        info = RequestInformation(Method.POST, "https://graph.microsoft.com/v1.0/me/sendMail")
        try:
            await adapter.send_no_response_content_async(info, {"5XX": ExampleError})
        except APIError as e:
            print(f"status {e.response_status_code}, message: {e.message!r}")
    print(f"spans never ended: {[s.name for s in started if s.end_time is None]}")


asyncio.run(main())
kiota-http 1.14.0
status 503, message: "The server returned an unexpected status code and the error registered for this code failed to deserialize: <class 'NoneType'>"
spans never ended: ['get_object_value']

Not covered by #713 (a body that cannot be parsed) or #724 (APIError.__str__); #508 was about other spans.

Fix: the status code in the message, as in kiota-dotnet; start the get_object_value span only when there is a body to deserialize, and end it in a finally, as get_root_parse_node does with its own span (#L423). With that, the script prints failed to deserialize: 503 and spans never ended: []. PR follows.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    • Status
      Done ✔️

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions