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

Merge 2xx responses with the same type and handle them in one block #1259

Open
RicoSuter opened this issue Apr 5, 2018 · 49 comments
Open

Merge 2xx responses with the same type and handle them in one block #1259

RicoSuter opened this issue Apr 5, 2018 · 49 comments

Comments

@RicoSuter
Copy link
Owner

merge 2xx responses with the same type and handle them in one if (status... || block

@jeremyVignelles
Copy link
Collaborator

If 204 (No content) is in the possible answers, just make the result nullable.

The point is : 2xx status codes should not throw in the client, because they semantically indicate success.

What would be the behavior if two different types are declared, e.g. for 200 and 201 ?

  • Will code generation for C# crash, since typescript can handle that?
  • Will that automatically create a wrapper?

@RicoSuter
Copy link
Owner Author

To avoid bigger breaking changes, I'd suggest to only merge the responses which do not require a changed return type otherwise generate the same code... for generating wrappers etc., we have additional settings (e.g. WrapResponses, etc.)

@Blackbaud-MitchellThomas

My team was caught off guard to see a 2xx response, in our case a 204, throw an exception, especially with a message in the exception of "Success", which is kind of a bizarre thing by itself. I would consider this more of a bug than an enhancement (how it's currently tagged).

We are totally fine with different blocks for each status code even with the same response type. Merging the code blocks is an optimization that I wouldn't hitch to a solution for this, especially with the complexity it would add.

Is anyone working on this?

@RicoSuter
Copy link
Owner Author

RicoSuter commented Sep 6, 2018

Is anyone working on this?

Probably not

@Blackbaud-MitchellThomas

I'll see if I can help out!

@dutchmartin
Copy link

I was surprised to see that a 201 throws an exception on a post operation since it is a positive response code.
https://httpstatuses.com/

@jchannon
Copy link

Just hit this by inspecting the generated code. Why is the default behaviour to throw if not 200 or 204. Seems a very odd decision. I would personally prefer if there was an option such as ThrowExceptions that hides this default feature. As a client I don't think I should be expecting an exception, it should be my responsibility to check the status code and process that how I want.

screen shot 2019-02-21 at 20 41 47

@kensykora
Copy link

The fact that this is tagged enhancement is really bizarre to me. 202 Accepted is super common in rest patterns for long running requests.

See https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#132-stepwise-long-running-operations

To me this is a bug with the library. I understand it'd cause backward compatibility issues, but a code generation flag, as was suggested earlier seems like a very reasonable solution. The fact that we need to handle this "normally" with a try/catch is very frustrating to me.

@RicoSuter
Copy link
Owner Author

As a client I don't think I should be expecting an exception, it should be my responsibility to check the status code and process that how I want.

The idea is to hide HTTP specific stuff (avoid leaky abstractions).

But i agree that 2xx should not throw but either return a result (200) or null (2xx) - if they dont have another model.. ie merge if possible. Would this be ok?

@xaviergxf
Copy link

Hi @RicoSuter any ideas implementation of this feature? When will this be available?

@ken2k
Copy link

ken2k commented Jun 2, 2020

Hi,

Anything new related to this issue?

@Jogai
Copy link

Jogai commented Jul 28, 2020

Can we expect the pr from @Trentm95 to get merged anywhere in the near future?

@Reintjuu
Copy link

@RicoSuter, what is the current state of this issue? Do you need help with merging, or is there some other issue I'm not seeing?

@jeremyVignelles
Copy link
Collaborator

I probably broke the PR by refactoring the status code handling. We need to rewrite it with the new status code handling

@jeremyVignelles
Copy link
Collaborator

Related to #2995 because both should be fixed at the same time : if a 204 is declared (or any empty response), the generated response type should be changed to T? (adds nullability to the response type), unless it is already void.

@nealsu
Copy link

nealsu commented Aug 18, 2020

Referencing item #1602, would be great to cater for the scenario mentioned there.

@jeremyVignelles
Copy link
Collaborator

@nealsu :please don't cross-post. issues are already linked and they will be fixed all at once

@jeremyVignelles
Copy link
Collaborator

I talked on gitter this evening with @RicoSuter and here is what we decided:

We need to merge the success (2xx) responses properly, to be able to fix issues like #1602 . But we need to be careful, because it will likely break things. Here is what was defined for the result merging:

  • Only applicable if WrapResponse is not used (no need for a merge in that case)
  • Can merge one or more success responses if they have the same response type
  • If one of the declared success responses has no type, it can merge with the result, but that makes the result nullable
  • If one of the declared success responses has a different type than the others, an exception is thrown in the C# generator (still discussing, see below)
  • Consequently, all the success cases are handled under the same if, the only one that has a return
  • It is worth a major version bump, to signal it's a breaking change.

If several different types are declared as success, we need to resolve the conflict. It doesn't seem to me that the conflict will occur in the typescript generator, because of the language's flexibility. What follows only applies to the C# client generator.

  • Either we throw if such conflict is encountered, crashing the generation. The message would suggest to use WrapResponse to be able to return different types and that would allow to accomodate funny specs.
  • Or we decide to generate a valid code anyway. Some success responses would be treated as returns, and others would throw a SwaggerException. Then question arises like "How do we decide which result is a return?" "Is that logical?" "Why does my 201 throws? You didn't fix anything!" etc...

I'm in favor of the first option.

Any opinion welcome.

@eholman
Copy link

eholman commented Sep 9, 2020

Maybe; I read it like a simple if-statement :-)

I have a POST operation that does not create a new resource but determines based on the incoming parameters what it needs to do. If all is OK it will return a string that contains an URL. In the other case there won't be an URL; hence the string? return type.

I have a collection Tickets. These can be paid; using a Payment Service Provider (external party -> URL redirect).

On my Tickets (= collection) Controller I have a method Pay bound to the route [Route("{transactionId}/Pay")] - so, in restful terms I want to initiate an operation on an item of a collection, right? According to specs this should be done via POST.

@clemensv
Copy link

clemensv commented Feb 4, 2021

Referencing related comment regarding 204 on issue closed as duplicate: #2499 (comment)

@jstensved
Copy link

How would you deserialize generics with Newtonsoft.Json in a generated code (which is meant to be generic, and not specific to your implementation), without having knowledge about what is going to be deserialized ? This is really tricky.

In the restful way of thinking, being able to return both 200 and 204 sounds like a bad API design : if it's not found (null), you should return 404 instead

204 doesn't mean "not found". It means request was successful but there is nothing to return.

Returning a 204 without content is useful when you're using an API for ingesting data that are put in a queue and then processed in the background. For instance an e-commerce system where the API acts as a front to just accept the order. As a calling client I might just want to know that it was received properly. The same goes for when adding things in a batch.

What should we return otherwise than 204 if the data is not changed? Returning the same data as the client sent doesn't add value.

@jeremyVignelles
Copy link
Collaborator

My point wasn't about the usefulness of the 204 by itself, it's about the API being able to return both 200 and 204.
I understand there is value in adding the feature, but I gave up trying to figure out how to implement that in NSwag, so this is up for grab.

@Bluejanis
Copy link

Whether to return 204 or 404 depends on the use case.
Both return no data, 404 implies that this is an error, while with 204 it is a successful operation.

I don't get why it is so difficult to return a nullable in the client, when the server returns one. The api falls behind basic c# functionality here. I don't care about the 204 status itself, if nullable data types would work, like they do in c#.

@jeremyVignelles
Copy link
Collaborator

The OpenAPI spec contains two response types, one for 200 (with the return type), one with 204 (without body).
Currently, NSwag loops through all the return codes it finds , and prints if/else there.

Handling 200/204 would not be easily doable in the template, so it would need to be handled before, in the generator, and a new propery must be added to handle the 200/204 separately.

By doing so, you're breaking the whole code base where it assumes there is a list of return types and probably other case I didn't see. The NSwag code base has been built incrementally, and now it's hard to make any change to it without refactoring everything, but feel free to try and make a PR.

@jorismathijssen
Copy link

jorismathijssen commented Oct 29, 2021

Hey all, I read this discussion and i understand that this is a difficult one.

Is there maybe a work around to support a 200 with a response and a 204 with no content for now?

image
This code from @jchannon would work in my situation but its not generated. Is this a setting or older nswag version?

@FH-Inway
Copy link

Also encountered an unexpected exception for an 204 status code. Would appreciate a solution or work around for this.

@vindberg
Copy link

vindberg commented Apr 29, 2022

Im having to wrap my object in a Nullable<T> to avoid this issue. Is there a better solution than using NSwag! I think Microsoft should allocate some resource to this project or promote a replacement. This can't continue!

@Jogai
Copy link

Jogai commented Apr 29, 2022

@vindberg your comment comes across as rude and ignorant. Microsoft should not cannibalize oss projects: https://aaronstannard.com/new-rules-dotnet-oss/

@vindberg
Copy link

@Jogai Im sorry for writing with such direct language. I know that Rico is doing a great job on the project - and thanks for that. The outstanding issues with NSwag could be fixed with some help from MS (just funding).

@ardalis
Copy link

ardalis commented Oct 27, 2022

This is still a problem. Anybody fixing it soon?

@asevos
Copy link

asevos commented Nov 14, 2022

Yep, still a problem. Can't find a workaround and have to redesign an endpoint so it would return some wrapper object instead of null. Feels like a crutch

@msharp1stdigital
Copy link

msharp1stdigital commented Jan 5, 2023

@asevos I proxy the generated client then put a try catch around like below - then I can catch the status code and return null:

public class ProxyXyz : IXyz {
     public override  async Task<Abc> GetSomethingThatAlsoReturnsNoContentAsync(int assetMasterId,  CancellationToken cancellationToken = default(CancellationToken))
    {
        try
        {
            // Call base method (but in try catch)
            return await base.GetSomethingThatAlsoReturnsNoContentAsync(assetMasterId, cancellationToken);
        }
        catch (AbcApiException ex)
        {
            // Here we want null NOT an error
            if (ex.StatusCode == (int)HttpStatusCode.NoContent)
                return null;

            // Continue to propagate the initial error if not matching our status code
            else throw ex;
        }
    }

This also sucks - but being between a rock and a hard place id rather this over the wrapper obj response

@TheXenocide
Copy link

I like the idea of this suggestion given out-of-box defaults currently break with APIs that return null that expect clients to deserialize the response to a null value (we had one converted from the older Web API platform before .NET Core). Given ASP.NET Core has a HttpNoContentOutputFormatter which is enabled by default, might it be appropriate to look for that on nullable return types when generating the Open API Specification document to determine if a 204 should be expected for null responses?

Currently we're removing that formatter to return a 200 with a serialized null in the response body, which is how things worked before, but in many cases it makes sense that it could be treated as a 204 (or a 404) that isn't considered an exception by the client. I think more control of this client-side functionality will help APIs and their associated clients be more pure in their RESTfulness so +1 from me.

@IanKemp
Copy link

IanKemp commented Nov 9, 2023

The fact that this is still a problem in 2023, half a decade after it was first raised, blows my mind. This is incredibly basic functionality that should've been the team's absolute highest priority to be implemented.

The fact that such fundamentals remain missing is why our team is strongly looking at writing our own HTTP client generator, and likely why Microsoft is doing the same.

@jvmlet
Copy link

jvmlet commented Nov 9, 2023

Great, @IanKemp , please don't forget to open source your implementation.
I suppose such contribution should be your highest priority given the fact that you are enjoying nswag for half a decade....

@IanKemp
Copy link

IanKemp commented Nov 9, 2023

Great, @IanKemp , please don't forget to open source your implementation. I suppose such contribution should be your highest priority given the fact that you are enjoying nswag for half a decade....

If your response to someone pointing out that not addressing an open issue in half a decade is a problem, is to attack that person, you need to reconsider your life choices.

@lahma
Copy link
Collaborator

lahma commented Nov 9, 2023

But who should address an open issue such like this? The person needing the feature or someone from the OSS project that tries to balance their free time?

I could imagine that if somebody considering how to use their free time, they might not share the same priorities like someone ordering features. Their highest priority might be health (mental or physical), their family or maybe some other hobby.

I'm quite sure that like many other OSS projects there might be possibility to get a refund if you're not getting your money's worth.

RicoSuter added the help wanted label on Sep 7, 2018

@jvmlet
Copy link

jvmlet commented Nov 9, 2023

If your response to someone pointing out that not addressing an open issue in half a decade is a problem, is to attack that person, you need to reconsider your life choices.

I think that the one that is complaining in such a manner about the issue in OS project should definitely reconsider HIS attitude and life choices.

@Eugene-Vector
Copy link

Eugene-Vector commented Dec 12, 2023

Five years without solution, must be real rocket science to fix...

How about introducing new optional .nswag configuration option, something like 'AllowedSuccessStatusCodes' containing comma-separated list. By default it's just '200'. If the option is not defined in .nswag file the client generation engine also treats it as '200'. If someone wants to extend it with other status codes (eg 204), he just adds it to the values list - '200,204' and it is reflected in if (status_ == ...) condition.
In this way no breaking changes would be introduced.

@ardalis
Copy link

ardalis commented Dec 12, 2023

When will your PR be ready to review?

@fuliii1
Copy link

fuliii1 commented Jan 12, 2024

I could fix code generation issue for 204 response with the following operation filter:

public class NullableResponseOperationFilter : IOperationFilter
{
    /// <inheritdoc />
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {
        foreach (var response in operation.Responses.Where(response => response.Key == "204"))
        {
            foreach (var content in response.Value.Content)
            {
                content.Value.Schema.Nullable = true;
            }
        }
    }
}

The action looks like this:

[HttpPost("{id:long}/action/{orderAction:orderAction}")]
[Produces(Constants.MimeTypes.Pdf, Type = typeof(FileStreamResult))]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status204NoContent)]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
public async Task<IActionResult> ExecuteActionAsync(int id, Models.Orders.Action orderAction)
{
    ...
}

The operation filter is used this way:

builder.Services.AddSwaggerGen(options =>
{
    options.OperationFilter<SwaggerDefaultValuesSessionIdParameter>();
});

@IanKemp
Copy link

IanKemp commented Feb 29, 2024

I could fix code generation issue for 204 response with the following operation filter:

public class NullableResponseOperationFilter : IOperationFilter
{
    /// <inheritdoc />
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {
        foreach (var response in operation.Responses.Where(response => response.Key == "204"))
        {
            foreach (var content in response.Value.Content)
            {
                content.Value.Schema.Nullable = true;
            }
        }
    }
}

The action looks like this:

[HttpPost("{id:long}/action/{orderAction:orderAction}")]
[Produces(Constants.MimeTypes.Pdf, Type = typeof(FileStreamResult))]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status204NoContent)]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
public async Task<IActionResult> ExecuteActionAsync(int id, Models.Orders.Action orderAction)
{
    ...
}

The operation filter is used this way:

builder.Services.AddSwaggerGen(options =>
{
    options.OperationFilter<SwaggerDefaultValuesSessionIdParameter>();
});

Uh, no, this doesn't work. NSwag still generates if response is 204 and null, then throw exception code.

@fuliii1
Copy link

fuliii1 commented Feb 29, 2024

Hi @IanKemp,

Could you provide your code snipet. This works for me.

Thanks,

@amithkumarg
Copy link

amithkumarg commented Apr 24, 2024

I could fix code generation issue for 204 response with the following operation filter:

public class NullableResponseOperationFilter : IOperationFilter
{
    /// <inheritdoc />
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {
        foreach (var response in operation.Responses.Where(response => response.Key == "204"))
        {
            foreach (var content in response.Value.Content)
            {
                content.Value.Schema.Nullable = true;
            }
        }
    }
}

The action looks like this:

[HttpPost("{id:long}/action/{orderAction:orderAction}")]
[Produces(Constants.MimeTypes.Pdf, Type = typeof(FileStreamResult))]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status204NoContent)]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
public async Task<IActionResult> ExecuteActionAsync(int id, Models.Orders.Action orderAction)
{
    ...
}

The operation filter is used this way:

builder.Services.AddSwaggerGen(options =>
{
    options.OperationFilter<SwaggerDefaultValuesSessionIdParameter>();
});

Totally worked for me. Thanks alot @fuliii1

@amithkumarg
Copy link

I also noticed just by adding this annotation to my Controller endpoint method, the issue is resolve in the generated ts & cs files.
[SwaggerResponse(HttpStatusCode.NoContent, typeof(void))]

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

No branches or pull requests