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

Exception for status code 204 using TypeSpec #5018

Open
AnReZa opened this issue Nov 11, 2024 · 3 comments
Open

Exception for status code 204 using TypeSpec #5018

AnReZa opened this issue Nov 11, 2024 · 3 comments

Comments

@AnReZa
Copy link

AnReZa commented Nov 11, 2024

I'm generating my server code with TypeSpec as a basis. For example the following method:

  @route("/{productionOrderNumber}")
  @get getOrderData(
    @path productionOrderNumber: string
  ): ProductionOrder | NoContentResponse | ProblemDetail;

The c# implementation will return 204, if no content could be found under this key. However, the code that Visual Studio's code generator generates looks as follows:

public virtual async System.Threading.Tasks.Task<ProductionOrder> GetOrderDataAsync(string productionOrderNumber, System.Threading.CancellationToken cancellationToken)
{
    if (productionOrderNumber == null)
        throw new System.ArgumentNullException("productionOrderNumber");

    var client_ = _httpClient;
    var disposeClient_ = false;
    try
    {
        using (var request_ = new System.Net.Http.HttpRequestMessage())
        {
            request_.Method = new System.Net.Http.HttpMethod("GET");
            request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));

            var urlBuilder_ = new System.Text.StringBuilder();
            if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl);
            // Operation Path: "productionOrders/{productionOrderNumber}"
            urlBuilder_.Append("productionOrders/");
            urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(productionOrderNumber, System.Globalization.CultureInfo.InvariantCulture)));

            PrepareRequest(client_, request_, urlBuilder_);

            var url_ = urlBuilder_.ToString();
            request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);

            PrepareRequest(client_, request_, url_);

            var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
            var disposeResponse_ = true;
            try
            {
                var headers_ = new System.Collections.Generic.Dictionary<string, System.Collections.Generic.IEnumerable<string>>();
                foreach (var item_ in response_.Headers)
                    headers_[item_.Key] = item_.Value;
                if (response_.Content != null && response_.Content.Headers != null)
                {
                    foreach (var item_ in response_.Content.Headers)
                        headers_[item_.Key] = item_.Value;
                }

                ProcessResponse(client_, response_);

                var status_ = (int)response_.StatusCode;
                if (status_ == 200)
                {
                    var objectResponse_ = await ReadObjectResponseAsync<ProductionOrder>(response_, headers_, cancellationToken).ConfigureAwait(false);
                    if (objectResponse_.Object == null)
                    {
                        throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
                    }
                    return objectResponse_.Object;
                }
                else
                if (status_ == 204)
                {
                    string responseText_ = ( response_.Content == null ) ? string.Empty : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
                    throw new ApiException("There is no content to send for this request, but the headers may be useful.", status_, responseText_, headers_, null);
                }
                else
                if (status_ == 500)
                {
                    var objectResponse_ = await ReadObjectResponseAsync<ProblemDetail>(response_, headers_, cancellationToken).ConfigureAwait(false);
                    if (objectResponse_.Object == null)
                    {
                        throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
                    }
                    throw new ApiException<ProblemDetail>("Server error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
                }
                else
                {
                    var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
                    throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
                }
            }
            finally
            {
                if (disposeResponse_)
                    response_.Dispose();
            }
        }
    }
    finally
    {
        if (disposeClient_)
            client_.Dispose();
    }
}

As you can see, it'll always throw an exception for status 204 instead of simply returning null, which would be the expected behavior. I also tried to modify the TypeSpec return type definition to ProductionOrder | null | ProblemDetail to allow returning null, but that lead to a similar result with an exception.

How can I fix that weird behavior without workarounds? Is there a flag that I can set for the generator? I read that people have been having similar problems for years now. Is there a solution that I missed? Am I doing something wrong?

@vvdb-architecture
Copy link

Same problem with C#, solvable with response "nullable" annotation. See GFlisch/Arc4u.Guidance.Doc#88

@ErikPilsits-RJW
Copy link
Contributor

ErikPilsits-RJW commented Nov 26, 2024

You could vote for my PR to be merged...

#4257

Otherwise you can use a local override of the template file which is what our org is doing right now.

@AnReZa
Copy link
Author

AnReZa commented Nov 28, 2024

As this repository seems to be pretty much dead, using the local override seems to be the way to go.

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

No branches or pull requests

3 participants