diff --git a/src/HotChocolate/Core/src/Types/Internal/TypeDiscoveryInfo.cs b/src/HotChocolate/Core/src/Types/Internal/TypeDiscoveryInfo.cs index 3b7df574797..34eeaa8cddc 100644 --- a/src/HotChocolate/Core/src/Types/Internal/TypeDiscoveryInfo.cs +++ b/src/HotChocolate/Core/src/Types/Internal/TypeDiscoveryInfo.cs @@ -135,18 +135,38 @@ private static bool IsComplexTypeInternal( IExtendedType unresolvedType, bool isPublic) { - var isComplexType = + var isComplexClass = isPublic && unresolvedType.Type.IsClass && unresolvedType.Type != typeof(string); - if (!isComplexType && unresolvedType.IsGeneric) +#if NET6_0_OR_GREATER + var isComplexValueType = + isPublic && + unresolvedType.Type is + { + IsValueType: true, + IsPrimitive: false, + IsEnum: false, + IsByRefLike: false + }; + + if (isComplexValueType && unresolvedType.IsGeneric) + { + var typeDefinition = unresolvedType.Definition; + return typeDefinition == typeof(KeyValuePair<,>); + } + + return isComplexClass || isComplexValueType; +#else + if (!isComplexClass && unresolvedType.IsGeneric) { var typeDefinition = unresolvedType.Definition; return typeDefinition == typeof(KeyValuePair<,>); } - return isComplexType; + return isComplexClass; +#endif } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/HotChocolate/Core/test/Types.Tests/Configuration/SchemaTypeResolverTests.cs b/src/HotChocolate/Core/test/Types.Tests/Configuration/SchemaTypeResolverTests.cs index 5b063641780..3d6b0d297a4 100644 --- a/src/HotChocolate/Core/test/Types.Tests/Configuration/SchemaTypeResolverTests.cs +++ b/src/HotChocolate/Core/test/Types.Tests/Configuration/SchemaTypeResolverTests.cs @@ -34,6 +34,47 @@ public void InferObjectType(TypeContext context) }); } + [InlineData(TypeContext.Output)] + [InlineData(TypeContext.None)] + [Theory] + public void InferObjectTypeFromStruct(TypeContext context) + { + // arrange + var descriptorContext = DescriptorContext.Create(); + var typeReference = TypeReference.Create(TypeOf(), context); + + // act + var success = descriptorContext.TryInferSchemaType(typeReference, out var schemaTypes); + + // assert + Assert.True(success); + Assert.Collection(schemaTypes, + type => + { + Assert.Equal(TypeContext.Output, type.Context); + Assert.Equal(typeof(ObjectType), ((ExtendedTypeReference)type).Type.Source); + }); + } + + [InlineData(TypeContext.Output)] + [InlineData(TypeContext.None)] + [Theory] + public void RejectRefStructAsObjectType(TypeContext context) + { + // arrange + var descriptorContext = DescriptorContext.Create(); + var typeReference = TypeReference.Create( + _typeInspector.GetType(typeof(BarRefStruct)), + context); + + // act + var success = descriptorContext.TryInferSchemaType(typeReference, out var schemaTypes); + + // assert + Assert.False(success); + Assert.Null(schemaTypes); + } + [InlineData(TypeContext.Output)] [InlineData(TypeContext.None)] [Theory] @@ -54,7 +95,6 @@ public void InferInterfaceType(TypeContext context) Assert.Equal(TypeContext.Output, type.Context); Assert.Equal(typeof(InterfaceType), ((ExtendedTypeReference)type).Type.Source); }); - } [Fact] @@ -108,6 +148,16 @@ public class Bar public string Baz { get; } } + public struct BarStruct + { + public string Baz { get; } + } + + public ref struct BarRefStruct + { + public string Baz { get; } + } + public interface IBar { string Baz { get; } diff --git a/src/HotChocolate/Core/test/Types.Tests/Configuration/TypeDiscoveryTests.cs b/src/HotChocolate/Core/test/Types.Tests/Configuration/TypeDiscoveryTests.cs index 73d560862b4..c65e6d45884 100644 --- a/src/HotChocolate/Core/test/Types.Tests/Configuration/TypeDiscoveryTests.cs +++ b/src/HotChocolate/Core/test/Types.Tests/Configuration/TypeDiscoveryTests.cs @@ -27,9 +27,20 @@ public void InferDateTimeFromModel() .MatchSnapshot(); } + [Fact] + public void TypeDiscovery_Should_InferStructs() + { + SchemaBuilder.New() + .AddQueryType() + .Create() + .Print() + .MatchSnapshot(); + } + public class QueryWithDateTime { public DateTimeOffset DateTimeOffset(DateTimeOffset time) => time; + public DateTime DateTime(DateTime time) => time; } @@ -50,8 +61,7 @@ protected override void Configure(IObjectTypeDescriptor descriptor) public class ModelType : ObjectType { - protected override void Configure( - IObjectTypeDescriptor descriptor) + protected override void Configure(IObjectTypeDescriptor descriptor) { descriptor.Field(t => t.Time) .Type>(); @@ -64,9 +74,39 @@ protected override void Configure( public class Model { public string Foo { get; set; } + public int Bar { get; set; } + public bool Baz { get; set; } + public DateTime Time { get; set; } + public DateTime Date { get; set; } } + + public struct InferStruct + { + public Guid Id { get; set; } + + public int Number { get; set; } + } + + public class QueryTypeWithStruct + { + public InferStruct Struct { get; set; } + + public InferStruct? NullableStruct { get; set; } + + public InferStruct[] StructArray { get; set; } + + public InferStruct?[] NullableStructArray { get; set; } + + public InferStruct[][] StructNestedArray { get; set; } + + public InferStruct?[][] NullableStructNestedArray { get; set; } + + public Guid ScalarGuid { get; set; } + + public DateTime ScalarDateTime { get; set; } + } } diff --git a/src/HotChocolate/Core/test/Types.Tests/Configuration/__snapshots__/TypeDiscoveryTests.TypeDiscovery_Should_InferStructs.snap b/src/HotChocolate/Core/test/Types.Tests/Configuration/__snapshots__/TypeDiscoveryTests.TypeDiscovery_Should_InferStructs.snap new file mode 100644 index 00000000000..79ed3fe5562 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Tests/Configuration/__snapshots__/TypeDiscoveryTests.TypeDiscovery_Should_InferStructs.snap @@ -0,0 +1,27 @@ +schema { + query: QueryTypeWithStruct +} + +type InferStruct { + id: UUID! + number: Int! +} + +type QueryTypeWithStruct { + struct: InferStruct! + nullableStruct: InferStruct + structArray: [InferStruct!] + nullableStructArray: [InferStruct] + structNestedArray: [[InferStruct!]] + nullableStructNestedArray: [[InferStruct]] + scalarGuid: UUID! + scalarDateTime: DateTime! +} + +"The `@specifiedBy` directive is used within the type system definition language to provide a URL for specifying the behavior of custom scalar definitions." +directive @specifiedBy("The specifiedBy URL points to a human-readable specification. This field will only read a result for scalar types." url: String!) on SCALAR + +"The `DateTime` scalar represents an ISO-8601 compliant date time type." +scalar DateTime @specifiedBy(url: "https:\/\/www.graphql-scalars.com\/date-time") + +scalar UUID @specifiedBy(url: "https:\/\/tools.ietf.org\/html\/rfc4122")