reactiveui / refit

The automatic type-safe REST library for .NET Core, Xamarin and .NET. Heavily inspired by Square's Retrofit library, Refit turns your REST API into a live interface.
https://reactiveui.github.io/refit/
MIT License
8.68k stars 745 forks source link

[Bug]: throw :Sequence contains no elements,When parameter contains Flags enum . #1478

Open xingzhenlu opened 1 year ago

xingzhenlu commented 1 year ago

Describe the bug 🐞

When requesting a webapi and the passed parameter contains an enum type with Flags Attribute,for example client.SomeMethod (MyEnum. Value1 | MyEnum. Value2 | MyEnum. Value3),throw InvalidOperationException: Sequence contains no elements.

Step to reproduce

for example client.SomeMethod (MyEnum. Value1 | MyEnum. Value2 | MyEnum. Value3)

Reproduction repository

Expected behavior

for example client.SomeMethod (MyEnum. Value1 | MyEnum. Value2 | MyEnum. Value3)

Screenshots 🖼️

No response

IDE

No response

Operating system

No response

Version

No response

Device

No response

ReactiveUI Version

No response

Additional information ℹ️

No response

TheMaakarov commented 7 months ago

A similar thing happend to me when passing an invalid enum value as a query parameter (i.e. (MyEnum)(-1)).

Seems that the exception is thrown at this line when calling the 'First' method.

Also, I noticed this issue is duplicated (although closed) at #794

goors commented 6 months ago

This is because you must define at least one flag in your request. Same thing happened to me.

Liandrel commented 2 months ago

This issue also occurs when passing an invalid enum value in the query. I encountered it while running integration tests for my application. It's a bit frustrating that the API gracefully returns a validation error, but the SDK throws an exception instead.

Liandrel commented 2 months ago

for all ppl in the future

image image image

that fixes the issue and exception is not beeing thrown anymore

TimothyMakkison commented 2 months ago

Thanks for raising this issue. I'll see if I can fix this tonight

TimothyMakkison commented 1 month ago

So I could add @Liandrel 's fix, but how should EnumMember be handled for flag enums? This comment suggest that refit used to comma/space separate enums with their EnumMemberAttribute. I could add that back but perhaps without the additional space.

TheMaakarov commented 1 month ago

Personally, I would rather that they were formatted as int values, but I guess that could break some more stuff...

Idk, I guess only trying and testing would show. Anyway, I guess it feels safer to implement your proposal by removing the whitespace

rikjeremy commented 2 weeks ago

I've just run into this while writing some API tests for our code.

Given the enum:

[Flags]
public enum Foo
{
    public Flag1 = 0x1,
    public Flag2 = 0x2
}

After playing around with the endpoint using Postman - for a query string, all those below correctly deserialise into the enum on the endpoint in .net8. I don't currently have an endpoint with that in a body so I can't see what's going on when the enum is inside json.

?flagField=flag1,flag2 ?flagField=3 ?flagField=flag1&flagField=flag2

From a debugging point of view it would be more friendly to have the names in the query string so we can see them in request logs without having to decode them every time, but if the int value is what is transmitted over the wire it's not the end of the world.

My solution (that works for my purposes) in the meantime was to handle the specific case in by inheriting the default handler and then handling the specific case of a bitmap enum and if those specific conditions aren't met passing it on to the base format function. It is worth noting that ToString on a bitmap enum produces a comma separated list of the set values. Is this easier than testing each value of the enum?

public class RefitFlagEnumFormatter : DefaultUrlParameterFormatter
{
    public override string? Format(object? value, ICustomAttributeProvider attributeProvider, Type type)
    {
        if (value != null && type.IsEnum && type.CustomAttributes.Any(o => o.AttributeType == typeof(FlagsAttribute)))
        {
            return value.ToString()?.Replace(" ", "");
        }

        return base.Format(value, attributeProvider, type);

    }
}

This just returns the set values as a comma separated list with the spaces removed. Refit handles the URL encoding of the comma.