diff --git a/src/HotChocolate/Core/src/Types/Types/Descriptors/Conventions/DefaultTypeInspector.cs b/src/HotChocolate/Core/src/Types/Types/Descriptors/Conventions/DefaultTypeInspector.cs index 2bb61fbe702..8e858272217 100644 --- a/src/HotChocolate/Core/src/Types/Types/Descriptors/Conventions/DefaultTypeInspector.cs +++ b/src/HotChocolate/Core/src/Types/Types/Descriptors/Conventions/DefaultTypeInspector.cs @@ -27,6 +27,7 @@ public class DefaultTypeInspector private readonly Dictionary _methods = new Dictionary(); + private readonly Dictionary _records = new Dictionary(); @@ -309,6 +310,11 @@ public virtual bool TryGetDefaultValue(PropertyInfo property, out object? defaul return true; } + if (TryGetDefaultValueFromConstructor(property, out defaultValue)) + { + return true; + } + defaultValue = null; return false; } @@ -566,9 +572,9 @@ private static bool HasConfiguration(ICustomAttributeProvider element) private static bool IsIgnored(MemberInfo member) { - if (IsCloneMember(member) || - IsToString(member) || - IsGetHashCode(member) || + if (IsCloneMember(member) || + IsToString(member) || + IsGetHashCode(member) || IsEquals(member)) { return true; @@ -597,6 +603,7 @@ private bool IsRecord(Type type) isRecord = IsRecord(type.GetMembers()); _records[type] = isRecord; } + return isRecord; } @@ -609,6 +616,7 @@ private static bool IsRecord(IReadOnlyList members) return true; } } + return false; } @@ -633,7 +641,8 @@ private static bool IsRecord(IReadOnlyList members) } private IEnumerable GetCustomAttributesFromRecord( - PropertyInfo property, bool inherit) + PropertyInfo property, + bool inherit) where T : Attribute { Type recordType = property.DeclaringType!; @@ -661,7 +670,8 @@ private static bool IsRecord(IReadOnlyList members) } private T? GetCustomAttributeFromRecord( - PropertyInfo property, bool inherit) + PropertyInfo property, + bool inherit) where T : Attribute { Type recordType = property.DeclaringType!; @@ -687,7 +697,8 @@ private static bool IsRecord(IReadOnlyList members) } private static bool IsDefinedOnRecord( - PropertyInfo property, bool inherit) + PropertyInfo property, + bool inherit) where T : Attribute { Type recordType = property.DeclaringType!; @@ -711,5 +722,29 @@ private static bool IsRecord(IReadOnlyList members) return false; } + + private bool TryGetDefaultValueFromConstructor( + PropertyInfo property, + out object? defaultValue) + { + defaultValue = null; + if (IsRecord(property.DeclaringType!)) + { + ConstructorInfo[] constructors = property.DeclaringType!.GetConstructors(); + + if (constructors.Length == 1) + { + foreach (ParameterInfo parameter in constructors[0].GetParameters()) + { + if (parameter.Name.EqualsOrdinal(property.Name)) + { + return TryGetDefaultValue(parameter, out defaultValue); + } + } + } + } + + return false; + } } -} \ No newline at end of file +} diff --git a/src/HotChocolate/Core/test/Types.Records.Tests/RecordsTests.cs b/src/HotChocolate/Core/test/Types.Records.Tests/RecordsTests.cs index e12a709980b..db6f4fab345 100644 --- a/src/HotChocolate/Core/test/Types.Records.Tests/RecordsTests.cs +++ b/src/HotChocolate/Core/test/Types.Records.Tests/RecordsTests.cs @@ -1,49 +1,71 @@ -using System; -using Microsoft.Extensions.DependencyInjection; -using HotChocolate.Types.Relay; -using static HotChocolate.Tests.TestHelper; -using System.Threading.Tasks; -using HotChocolate.Tests; -using Snapshooter.Xunit; -using Xunit; -using HotChocolate.Execution; - -namespace HotChocolate.Types -{ - public class RecordsTests - { - [Fact] - public async Task Records_Clone_Member_Is_Removed() - { - Snapshot.FullName(); - - await new ServiceCollection() - .AddGraphQL() - .AddQueryType() - .Services - .BuildServiceProvider() - .GetSchemaAsync() - .MatchSnapshotAsync(); - } - - [Fact] - public async Task Relay_Id_Middleware_Is_Correctly_Applied() - { - Snapshot.FullName(); - - await ExpectValid - ( - @"{ person { id name } }", - b => b.AddQueryType() - ) - .MatchSnapshotAsync(); ; - } - - public class Query - { - public Person GetPerson() => new Person(1, "Michael"); - } - - public record Person([ID] int Id, string Name); - } -} +using System; +using Microsoft.Extensions.DependencyInjection; +using HotChocolate.Types.Relay; +using static HotChocolate.Tests.TestHelper; +using System.Threading.Tasks; +using HotChocolate.Tests; +using Snapshooter.Xunit; +using Xunit; +using HotChocolate.Execution; + +namespace HotChocolate.Types +{ + public class RecordsTests + { + [Fact] + public async Task Records_Clone_Member_Is_Removed() + { + Snapshot.FullName(); + + await new ServiceCollection() + .AddGraphQL() + .AddQueryType() + .Services + .BuildServiceProvider() + .GetSchemaAsync() + .MatchSnapshotAsync(); + } + + [Fact] + public async Task Records_Default_Value_Is_Taken_From_Ctor() + { + Snapshot.FullName(); + + await new ServiceCollection() + .AddGraphQL() + .AddQueryType() + .Services + .BuildServiceProvider() + .GetSchemaAsync() + .MatchSnapshotAsync(); + } + + [Fact] + public async Task Relay_Id_Middleware_Is_Correctly_Applied() + { + Snapshot.FullName(); + + await ExpectValid + ( + @"{ person { id name } }", + b => b.AddQueryType() + ) + .MatchSnapshotAsync(); + } + + public class Query + { + public Person GetPerson() => new Person(1, "Michael"); + } + + public record Person([ID] int Id, string Name); + + public class Query2 + { + public DefaultValueTest GetPerson(DefaultValueTest? defaultValueTest) => + new DefaultValueTest(1, "Test"); + } + + public record DefaultValueTest([ID] int Id, string Name = "ShouldBeDefaultValue"); + } +} diff --git a/src/HotChocolate/Core/test/Types.Records.Tests/__snapshots__/RecordsTests.Records_Default_Value_Is_Taken_From_Ctor.snap b/src/HotChocolate/Core/test/Types.Records.Tests/__snapshots__/RecordsTests.Records_Default_Value_Is_Taken_From_Ctor.snap new file mode 100644 index 00000000000..e0aad812c2a --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Records.Tests/__snapshots__/RecordsTests.Records_Default_Value_Is_Taken_From_Ctor.snap @@ -0,0 +1,23 @@ +schema { + query: Query2 +} + +type DefaultValueTest { + id: ID! + name: String! +} + +type Query2 { + person(defaultValueTest: DefaultValueTestInput): DefaultValueTest! +} + +input DefaultValueTestInput { + id: ID! + name: String! = "ShouldBeDefaultValue" +} + +"The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\"4\"`) or integer (such as `4`) input value will be accepted as an ID." +scalar ID + +"The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text." +scalar String