C
C#ā€¢12mo ago
UltraWelfare

Interface using functional `Match` does not work

I'm using the OneOf library for discriminated unions
private static async Task<Results<Created<CustomerResult>, NotFound>> CreateCustomer(
[FromBody] CreateCustomerParameters parameters, CustomerService customerService
)
{
// this is OneOf<CustomerResult, ValueNotFound>
var result = await customerService.CreateCustomerAsync(parameters);

return result.Match<IResult>
(x => TypedResults.Created($"/customers/{x.Id}", x), _ => TypedResults.NotFound());
}
private static async Task<Results<Created<CustomerResult>, NotFound>> CreateCustomer(
[FromBody] CreateCustomerParameters parameters, CustomerService customerService
)
{
// this is OneOf<CustomerResult, ValueNotFound>
var result = await customerService.CreateCustomerAsync(parameters);

return result.Match<IResult>
(x => TypedResults.Created($"/customers/{x.Id}", x), _ => TypedResults.NotFound());
}
I have to use TypedResults in order to get OpenAPI to understand things. For those unaware of the library Match works as public TResult Match<TResult>(Func<T0,TResult> f0, Func<T1,TResult> f1) So it will either call the first lambda if CreateCustomerAsync returns a CustomerResult or the second lambda if it returns ValueNotFound TypedResults.Created and TypedResults.NotFound() both extend the IResult interface, so I hoped it would work but it seems like it doesn't Cannot convert expression type 'Microsoft.AspNetCore.Http.IResult' to return type 'Microsoft.AspNetCore.Http.HttpResults.Results<Microsoft.AspNetCore.Http.HttpResults.Created<Support.Features.Customers.CreateCustomer.CreateCustomerResponse>,Microsoft.AspNetCore.Http.HttpResults.NotFound>'
31 Replies
mg
mgā€¢12mo ago
The types in your return type and the type passed to Match aren't the same
UltraWelfare
UltraWelfareOPā€¢12mo ago
I know but since both Created and NotFound extend the same interface, shouldn't it work ? šŸ˜¦
mg
mgā€¢12mo ago
I'm not sure about the details of Match. I've run into a similar problem with OneOf But if Match is returning an IResult, that's what your method needs to return
UltraWelfare
UltraWelfareOPā€¢12mo ago
I guess it can't cast it back to Results<> then
mg
mgā€¢12mo ago
You need to get Match to return a Results<Created<CustomerResult>, NotFound>> Yeah, certainly not implicitly Let me see how I did this with OneOf
UltraWelfare
UltraWelfareOPā€¢12mo ago
I casted it, and it simply went boom I used this inside Match<> and it exploded inside the first lambda: Cannot convert expression type 'Microsoft.AspNetCore.Http.HttpResults.Created<Support.Entities.Customer>' to return type 'Microsoft.AspNetCore.Http.HttpResults.Results<Microsoft.AspNetCore.Http.HttpResults.Created<Support.Features.Customers.CreateCustomer.CreateCustomerResponse>,Microsoft.AspNetCore.Http.HttpResults.NotFound>' It can't convert Created into Results
mg
mgā€¢12mo ago
This is an API endpoint right?
UltraWelfare
UltraWelfareOPā€¢12mo ago
Yeah! using minimal api
mg
mgā€¢12mo ago
Make the method return IResult I think that's what it should be returning anyway
UltraWelfare
UltraWelfareOPā€¢12mo ago
This breaks OpenAPI
mg
mgā€¢12mo ago
This is my endpoint's entire body
var result = await itemService.GetByIdAsync(id, token);
return result.Match(
item => Results.Ok(((Material)item).MapToResponse()),
notFound => Results.NotFound());
var result = await itemService.GetByIdAsync(id, token);
return result.Match(
item => Results.Ok(((Material)item).MapToResponse()),
notFound => Results.NotFound());
UltraWelfare
UltraWelfareOPā€¢12mo ago
I have to use TypedResults in order to get OpenAPI to understand things. that's why I said this hahaha
mg
mgā€¢12mo ago
Ahhh right This is the exact problem I had let me see which endpoint it was Oh wait this is that endpoint Here's the whole endpoint
app.MapGet(ApiEndpoints.Inventory.Items.Materials.Get, async (
int id,
[FromServices] IItemService itemService,
CancellationToken token) =>
{
var result = await itemService.GetByIdAsync(id, token);
return result.Match(
item => Results.Ok(((Material)item).MapToResponse()),
notFound => Results.NotFound());
})
.Produces<MaterialResponse>()
.Produces(404)
.WithName(Name);
app.MapGet(ApiEndpoints.Inventory.Items.Materials.Get, async (
int id,
[FromServices] IItemService itemService,
CancellationToken token) =>
{
var result = await itemService.GetByIdAsync(id, token);
return result.Match(
item => Results.Ok(((Material)item).MapToResponse()),
notFound => Results.NotFound());
})
.Produces<MaterialResponse>()
.Produces(404)
.WithName(Name);
I think using Produces is how I worked around it
UltraWelfare
UltraWelfareOPā€¢12mo ago
That's not a bad idea tbh
mg
mgā€¢12mo ago
Yeah, I think the only issue is that for whatever reason, that doesn't work with multiple codes Although it should with multiple invocations Produces<TNotFound>(404), Produces<TBadRequest>(400), etc
UltraWelfare
UltraWelfareOPā€¢12mo ago
The only thing I don't like about Produces is that you can mess up the IResult and you'll have no compiler to complain
mg
mgā€¢12mo ago
Yeah this is weird Cause you're right that the typed results implement IResult so they should work I gotta go for now but I'm interested in this myself now so I'll get back to it later if you haven't solved it lol
Thinker
Thinkerā€¢12mo ago
Yeah the compiler in this case sees two conflicting possibilities for the type of TResult. You have to explicitly specify that TResult is IResult for this to work.
UltraWelfare
UltraWelfareOPā€¢12mo ago
That's the problem.. it doesn't work
return result.Match<IResult>
(x => (IResult)TypedResults.Created($"/customers/{x.Id}", x), _ => (IResult)TypedResults.NotFound());
return result.Match<IResult>
(x => (IResult)TypedResults.Created($"/customers/{x.Id}", x), _ => (IResult)TypedResults.NotFound());
not even this works it says that the cast is redundant
Thinker
Thinkerā€¢12mo ago
Does it not compile or does it just give you those redundancy warnings?
UltraWelfare
UltraWelfareOPā€¢12mo ago
Not compile Cannot convert expression type 'Microsoft.AspNetCore.Http.IResult' to return type 'Microsoft.AspNetCore.Http.HttpResults.Results<Microsoft.AspNetCore.Http.HttpResults.Created<Support.Features.Customers.CreateCustomer.CreateCustomerResponse>,Microsoft.AspNetCore.Http.HttpResults.NotFound>'
Thinker
Thinkerā€¢12mo ago
That's... odd
UltraWelfare
UltraWelfareOPā€¢12mo ago
public sealed class Results<[DynamicallyAccessedMembers(ResultsOfTHelper.RequireMethods)] TResult1, [DynamicallyAccessedMembers(ResultsOfTHelper.RequireMethods)] TResult2> : IResult, INestedHttpResult, IEndpointMetadataProvider
where TResult1 : IResult
where TResult2 : IResult
{
public sealed class Results<[DynamicallyAccessedMembers(ResultsOfTHelper.RequireMethods)] TResult1, [DynamicallyAccessedMembers(ResultsOfTHelper.RequireMethods)] TResult2> : IResult, INestedHttpResult, IEndpointMetadataProvider
where TResult1 : IResult
where TResult2 : IResult
{
šŸ¤” the problem is casting it back to the Results of the return
Thinker
Thinkerā€¢12mo ago
Wait you're returning Results<Created<CustomerResult>> from the lambda
UltraWelfare
UltraWelfareOPā€¢12mo ago
the Match<IResult> works yes
Thinker
Thinkerā€¢12mo ago
So then yeah you quite obviously cannot convert IResult to that (as already mentioned)
UltraWelfare
UltraWelfareOPā€¢12mo ago
I want to :((((((
Thinker
Thinkerā€¢12mo ago
It doesn't make sense You can't implicitly convert an interface to a concrete type
UltraWelfare
UltraWelfareOPā€¢12mo ago
how else should I go about doing it turns out i was returning the wrong type from the service:) All you have to do is
private static async Task<Results<Created<CustomerResult>, NotFound>> CreateCustomer(
[FromBody] CreateCustomerParameters parameters, CustomerService customerService
)
{
// this is OneOf<CustomerResult, ValueNotFound>
var result = await customerService.CreateCustomerAsync(parameters);

return result.Match<Results<Created<CustomerResult>, NotFound>> // <--- type here
(x => TypedResults.Created($"/customers/{x.Id}", x), _ => TypedResults.NotFound());
}
private static async Task<Results<Created<CustomerResult>, NotFound>> CreateCustomer(
[FromBody] CreateCustomerParameters parameters, CustomerService customerService
)
{
// this is OneOf<CustomerResult, ValueNotFound>
var result = await customerService.CreateCustomerAsync(parameters);

return result.Match<Results<Created<CustomerResult>, NotFound>> // <--- type here
(x => TypedResults.Created($"/customers/{x.Id}", x), _ => TypedResults.NotFound());
}
type it there it was just that I didn't return CustomerResult from the service, accidently:) That's why it wasn't working:) i'm dumb:)
mg
mgā€¢12mo ago
Oh ok, my issue months ago was that I was trying to just cast what Match returns instead of calling Match<IResult>
UltraWelfare
UltraWelfareOPā€¢12mo ago
nope just pass the return type inside match as it's generic argument and it's fine although a bit verbose šŸ˜¢
Want results from more Discord servers?
Add your server