Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,16 @@ private static (string TypeStructure, string TypeDefinition, string? Nullability
}

var typeName = GetFullyQualifiedTypeName(underlyingType);

if (IsFlagsEnumType(underlyingType))
{
return (
string.Empty,
typeName,
CreateNullabilityLiteral(underlyingType, isNullable),
IsSimpleType: true);
}

var compliantTypeName = MakeGraphQLCompliant(typeName);
var nullability = ShouldPreserveNullability(underlyingType)
? CreateNullabilityLiteral(underlyingType, isNullable)
Expand Down Expand Up @@ -328,6 +338,24 @@ private static bool IsArrayType(ITypeSymbol namedType, [NotNullWhen(true)] out I
return false;
}

private static bool IsFlagsEnumType(ITypeSymbol typeSymbol)
{
if (typeSymbol.TypeKind != TypeKind.Enum)
{
return false;
}

foreach (var attribute in typeSymbol.GetAttributes())
{
if (attribute.AttributeClass?.ToDisplayString() == WellKnownAttributes.FlagsAttribute)
{
return true;
}
}

return false;
}

private static bool TryGetListElementType(
INamedTypeSymbol namedType,
[NotNullWhen(true)] out ITypeSymbol? elementType)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public static class WellKnownAttributes
public const string DescriptorAttribute = "HotChocolate.Types.DescriptorAttribute";
public const string GraphQLDeprecatedAttribute = "HotChocolate.GraphQLDeprecatedAttribute";
public const string ObsoleteAttribute = "System.ObsoleteAttribute";
public const string FlagsAttribute = "System.FlagsAttribute";
public const string BatchResolverAttribute = "HotChocolate.Types.BatchResolverAttribute";
public const string GraphQLTypeAttribute = "HotChocolate.GraphQLTypeAttribute";
public const string IsSelectedAttribute = "HotChocolate.Types.IsSelectedAttribute";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
namespace HotChocolate.Types;

[Flags]
public enum AnimalKind
{
Dog = 1,
Cat = 2
}

public class Animal
{
public required string Id { get; set; }

public required AnimalKind Kind { get; set; }
}

public class Zoo
{
public required string Id { get; set; }
}

[ObjectType<Zoo>]
public static partial class ZooType
{
public static AnimalKind GetAnimals([Parent] Zoo zoo)
=> AnimalKind.Dog | AnimalKind.Cat;
}

public static partial class Query
{
public static Animal GetAnimal()
=> new() { Id = "1", Kind = AnimalKind.Dog | AnimalKind.Cat };

public static Zoo GetZoo()
=> new() { Id = "1" };
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using HotChocolate.Execution;
using Microsoft.Extensions.DependencyInjection;

namespace HotChocolate.Types;

public class FlagsEnumMethodBindingTests
{
[Fact]
public async Task FlagsEnum_Should_CoerceToFlagsObject_When_ReturnedFromPropertyBoundField()
{
// arrange
var executor = await new ServiceCollection()
.AddGraphQLServer()
.AddIntegrationTestTypes()
.AddPagingArguments()
.ModifyOptions(o => o.EnableFlagEnums = true)
.BuildRequestExecutorAsync(cancellationToken: TestContext.Current.CancellationToken);

// act
var result = await executor.ExecuteAsync(
"{ animal { kind { isDog isCat } } }",
TestContext.Current.CancellationToken);

// assert
result.MatchInlineSnapshot(
"""
{
"data": {
"animal": {
"kind": {
"isDog": true,
"isCat": true
}
}
}
}
""");
}

[Fact]
public async Task FlagsEnum_Should_CoerceToFlagsObject_When_ReturnedFromMethodBoundField()
{
// arrange
var executor = await new ServiceCollection()
.AddGraphQLServer()
.AddIntegrationTestTypes()
.AddPagingArguments()
.ModifyOptions(o => o.EnableFlagEnums = true)
.BuildRequestExecutorAsync(cancellationToken: TestContext.Current.CancellationToken);

// act
var result = await executor.ExecuteAsync(
"{ zoo { animals { isDog isCat } } }",
TestContext.Current.CancellationToken);

// assert
result.MatchInlineSnapshot(
"""
{
"data": {
"zoo": {
"animals": {
"isDog": true,
"isCat": true
}
}
}
}
""");
}
}
Loading