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

InvalidOperationException: Cannot return as T0 as result is T2 #188

Open
MrYossu opened this issue Nov 25, 2024 · 2 comments
Open

InvalidOperationException: Cannot return as T0 as result is T2 #188

MrYossu opened this issue Nov 25, 2024 · 2 comments

Comments

@MrYossu
Copy link

MrYossu commented Nov 25, 2024

Just getting started with this excellent package, so please forgive me if I'm doing something stupid.

I have an API, and want to create a response type that will indicate if the request was successful (in which case we return the data requested), if the specified object wasn't found, or if there was a server error.

I created the following...

public class ApiResponse<T>(OneOf<Error<string>, NotFound, T> input) : OneOfBase<Error<string>, NotFound, T>(input) {
  public static implicit operator ApiResponse<T>(Exception ex) => new(new Error<string>(ex.Message));
}

This mostly works fine, but throws an exception if the data to be returned is a plain string, as in this rather dumb test case...

    app.MapGet("/testapi-time", () =>
      new ApiResponse<string>(DateTime.Now.ToLongTimeString()));

In this case, I get the exception shown in the subject line. I guess this is because both the data and the generic type of Error are string, so it doesn't know which to use, but could be way out here.

I guess I could get around this by declaring the type by inheriting from OneOfBase<Error<string>, NotFound, Success<T>>, but that makes the code more verbose for the few cases when I would be returning string data.

Anyone able to advise? Thanks

Update: I just tried changing the endpoint to return a DateTime instead of a string...

    app.MapGet("/testapi-time", () =>
      new ApiResponse<DateTime>(DateTime.Now));

...and got the same exception, so it looks like my guess above was wrong.

@MrYossu
Copy link
Author

MrYossu commented Nov 25, 2024

Hmm, just realised that creating the ApiResponse works fine. If I run the following...

ApiResponse<DateTime> date = new ApiResponse<DateTime>(DateTime.Now);

...then it works fine. However, if I then try to dump it out...

Console.WriteLine(date);

...then I get the exception.

Given that I'm creating this in an API, I wonder if the problem comes when the framework tries to serialise it.

@StefanLochauMF
Copy link

OneOf<T0,...> or OneOfBase<T0,...> have properties which are implemented like

public T0 AsT0 =>
    _index == 0 ?
        _value0 :
        throw new InvalidOperationException($"Cannot return as T0 as result is T{_index}"); 

After you return your object from the lambda in MapGet, ASP.NET core will serialize your response object and then send that over HTTP back to the client.

For the serialization, usually System.Text.Json is used, which goes through all the public properties of a class and serializes them recursively. Once it hits the AsT0 property, an exception is thrown and the serialization fails.

To prevent this, I'd suggest you use Results<T0, ...> from the Microsoft.AspNetCore.Http.HttpResults namespace (Source). This has the benefit that the proper status codes for each of the response types are used and that your OpenApi-spec can be generated automatically.

For your usecase this could be something like

app.MapGet(
    "/testapi-time",
    () =>
    {
        var response =new ApiResponse<string>(DateTime.Now.ToLongTimeString());

        return response
            .Match<Results<ProblemHttpResult, Microsoft.AspNetCore.Http.HttpResults.NotFound, Ok<string>>>(
                error => TypedResults.Problem(
                    detail: error.Value,
                    statusCode: StatusCodes.Status500InternalServerError),
                notFound => TypedResults.NotFound(),
                okValue => TypedResults.Ok(okValue));
    });

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

2 participants