diff --git a/src/HotChocolate/Data/src/Data/Sorting/Convention/SortConvention.cs b/src/HotChocolate/Data/src/Data/Sorting/Convention/SortConvention.cs index e4b1b13df3e..1e094a99485 100644 --- a/src/HotChocolate/Data/src/Data/Sorting/Convention/SortConvention.cs +++ b/src/HotChocolate/Data/src/Data/Sorting/Convention/SortConvention.cs @@ -284,6 +284,14 @@ private bool TryCreateSortType( return false; } + //Record struct will have a generated PrintMembers method while guid/decimals won't + if (runtimeType.Type is { IsValueType: true, IsPrimitive: false } && + runtimeType.Type.GetMethod("PrintMembers", BindingFlags.NonPublic | BindingFlags.Instance) != null) + { + type = typeof(SortInputType<>).MakeGenericType(runtimeType.Type); + return true; + } + if (runtimeType.Type.IsClass || runtimeType.Type.IsInterface) { diff --git a/src/HotChocolate/Data/test/Data.Sorting.Tests/SortInputStructTypesTests.cs b/src/HotChocolate/Data/test/Data.Sorting.Tests/SortInputStructTypesTests.cs new file mode 100644 index 00000000000..f65be9f1afa --- /dev/null +++ b/src/HotChocolate/Data/test/Data.Sorting.Tests/SortInputStructTypesTests.cs @@ -0,0 +1,72 @@ +using CookieCrumble; +using HotChocolate.Data.Sorting; + +namespace HotChocolate.Data.Tests; + +public class SortInputStructTypesTest : SortTestBase +{ + + [Fact] + public void SortInputType_RecordStructSortableProperties() + { + // arrange + var builder = SchemaBuilder.New() + .AddQueryType() + .AddSorting(); + + // act + // assert + builder.Create().MatchSnapshot(); + } + + [Fact] + public void SortInputType_RecordStructSortableNestedWithSortInputType() + { + // arrange + var builder = SchemaBuilder.New() + .AddQueryType() + .AddSorting(); + + // act + // assert + builder.Create().MatchSnapshot(); + } + + + public class AccountsQuery + { + [UseSorting] + public IQueryable Accounts() => new List().AsQueryable(); + } + + public record struct Account(Guid CorrelationId, string AccountKey, DateOnly BalanceDate, DateTime Created); + + public class TestObjectsQuery + { + [UseSorting] + public IQueryable TestObjects() => new List().AsQueryable(); + } + + + public record struct TestObject + { + public Guid ObjectId { get; init; } + public bool IsRoot { get; init; } + public ChildObject Child { get; init; } + } + + public record struct ChildObject(NestedId Id, double Amount, decimal Sum); + + public record NestedId + { + public Guid ObjectId { get; init; } + } + + public class TestObjectSortInputType : SortInputType + { + protected override void Configure(ISortInputTypeDescriptor descriptor) + { + descriptor.Ignore(x => x.IsRoot); + } + } +} diff --git a/src/HotChocolate/Data/test/Data.Sorting.Tests/__snapshots__/SortInputStructTypesTest.SortInputType_RecordStructSortableNestedWithSortInputType.graphql b/src/HotChocolate/Data/test/Data.Sorting.Tests/__snapshots__/SortInputStructTypesTest.SortInputType_RecordStructSortableNestedWithSortInputType.graphql new file mode 100644 index 00000000000..ae298da2699 --- /dev/null +++ b/src/HotChocolate/Data/test/Data.Sorting.Tests/__snapshots__/SortInputStructTypesTest.SortInputType_RecordStructSortableNestedWithSortInputType.graphql @@ -0,0 +1,51 @@ +schema { + query: TestObjectsQuery +} + +type ChildObject { + id: NestedId! + amount: Float! + sum: Decimal! +} + +type NestedId { + objectId: UUID! +} + +type TestObject { + objectId: UUID! + isRoot: Boolean! + child: ChildObject! +} + +type TestObjectsQuery { + testObjects(order: [TestObjectSortInput!]): [TestObject!]! +} + +input ChildObjectSortInput { + id: NestedIdSortInput + amount: SortEnumType + sum: SortEnumType +} + +input NestedIdSortInput { + objectId: SortEnumType +} + +input TestObjectSortInput { + objectId: SortEnumType + child: ChildObjectSortInput +} + +enum SortEnumType { + ASC + DESC +} + +"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 built-in `Decimal` scalar type." +scalar Decimal + +scalar UUID @specifiedBy(url: "https:\/\/tools.ietf.org\/html\/rfc4122") \ No newline at end of file diff --git a/src/HotChocolate/Data/test/Data.Sorting.Tests/__snapshots__/SortInputStructTypesTest.SortInputType_RecordStructSortableProperties.graphql b/src/HotChocolate/Data/test/Data.Sorting.Tests/__snapshots__/SortInputStructTypesTest.SortInputType_RecordStructSortableProperties.graphql new file mode 100644 index 00000000000..8dc77cc7737 --- /dev/null +++ b/src/HotChocolate/Data/test/Data.Sorting.Tests/__snapshots__/SortInputStructTypesTest.SortInputType_RecordStructSortableProperties.graphql @@ -0,0 +1,37 @@ +schema { + query: AccountsQuery +} + +type Account { + correlationId: UUID! + accountKey: String! + balanceDate: Date! + created: DateTime! +} + +type AccountsQuery { + accounts(order: [AccountSortInput!]): [Account!]! +} + +input AccountSortInput { + correlationId: SortEnumType + accountKey: SortEnumType + balanceDate: SortEnumType + created: SortEnumType +} + +enum SortEnumType { + ASC + DESC +} + +"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 `Date` scalar represents an ISO-8601 compliant date type." +scalar Date + +"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") \ No newline at end of file