Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 35 additions & 10 deletions src/HotChocolate/Data/src/Data/Sorting/Context/SortingContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -158,11 +158,22 @@ protected override ISyntaxVisitorAction Enter(
Context context)
{
var type = context.Types.Peek();
var field = (SortField)type.Fields[node.Name.Value];
var fieldType = field.Type.NamedType();
if (!type.Fields.TryGetField(node.Name.Value, out var inputField))
{
context.Parents.Push(null);
return base.Leave(node, context);
}

var parent = context.Parents.Peek();
context.Parents.Push(Expression.Property(parent, (PropertyInfo)field.Member!));
var fieldType = inputField.Type.NamedType();

if (inputField is SortField field)
{
context.Parents.Push(CreateSelector(context.Parents.Peek(), field.Member));
}
else
{
context.Parents.Push(null);
}

if (fieldType.IsInputObjectType())
{
Expand All @@ -178,35 +189,49 @@ protected override ISyntaxVisitorAction Leave(
{
var type = context.Types.Peek();

if (type.Fields.TryGetField(node.Name.Value, out var inputField) && inputField is SortField sortField)
if (type.Fields.TryGetField(node.Name.Value, out var inputField))
{
var fieldType = sortField.Type.NamedType();
var fieldType = inputField.Type.NamedType();
var expression = context.Parents.Pop();

if (fieldType.IsInputObjectType())
{
context.Types.Pop();
}
else
else if (inputField is SortField && expression is not null)
{
var ascending = node.Value.Value?.Equals("ASC") ?? true;
context.Completed.Add((expression, ascending, sortField.Member!.GetReturnType()));
context.Completed.Add((expression, ascending, expression.Type));
}
}
else
{
context.Types.Pop();
context.Parents.Pop();
}

return base.Leave(node, context);
}

private static Expression? CreateSelector(Expression? parent, MemberInfo? member)
{
if (parent is null || member is null)
{
return null;
}

return member switch
{
PropertyInfo property => Expression.Property(parent, property),
FieldInfo field => Expression.Field(parent, field),
_ => null
};
}

public class Context
{
public Stack<InputObjectType> Types { get; } = new();

public Stack<Expression> Parents { get; } = new();
public Stack<Expression?> Parents { get; } = new();

public List<(Expression, bool, Type)> Completed { get; } = [];
}
Expand Down
94 changes: 94 additions & 0 deletions src/HotChocolate/Data/test/Data.Tests/IntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// ReSharper disable MoveLocalFunctionAfterJumpStatement

using GreenDonut.Data;
using HotChocolate.Configuration;
using HotChocolate.Data.Filters;
using HotChocolate.Data.Sorting;
using HotChocolate.Execution;
Expand Down Expand Up @@ -1067,6 +1068,31 @@ name @include(if: $withName)
.Match();
}

[Fact]
public async Task AsSortDefinition_QueryContext_Custom_Field_Without_Member_Does_Not_Fail()
{
// arrange
var executor = await new ServiceCollection()
.AddGraphQL()
.AddSorting()
.AddQueryType<QueryContextCustomSortQuery>()
.BuildRequestExecutorAsync();

// act
var result = await executor.ExecuteAsync(
"""
{
customSortBooks(order: [{ metadata: { fieldId: 42, direction: ASC } }]) {
id
title
}
}
""");

// assert
result.MatchSnapshot();
}

[QueryType]
public static class StaticQuery
{
Expand Down Expand Up @@ -1354,4 +1380,72 @@ public string Name
set => _name = value;
}
}

public class QueryContextCustomSortQuery
{
[UseSorting(typeof(CustomSortBookSortType))]
public IQueryable<CustomSortBook> GetCustomSortBooks(QueryContext<CustomSortBook> context)
=> new[]
{
new CustomSortBook
{
Id = 1,
Title = "Zebra",
Metadata = []
},
new CustomSortBook
{
Id = 2,
Title = "Apple",
Metadata = []
}
}.AsQueryable().With(context);
}

public class CustomSortBook
{
public int Id { get; set; }

public string Title { get; set; } = string.Empty;

public List<CustomSortBookMetadata> Metadata { get; set; } = [];
}

public class CustomSortBookMetadata
{
public int FieldId { get; set; }

public string Value { get; set; } = string.Empty;
}

public class CustomSortMetadataInputType : InputObjectType
{
protected override void Configure(IInputObjectTypeDescriptor descriptor)
{
descriptor.Field("fieldId").Type<IntType>();
descriptor.Field("direction").Type<DefaultSortEnumType>();
}
}

public class CustomSortFieldHandler : ISortFieldHandler
{
public bool CanHandle(
ITypeCompletionContext context,
ISortInputTypeConfiguration typeConfiguration,
ISortFieldConfiguration fieldConfiguration)
=> true;
}

public class CustomSortBookSortType : SortInputType<CustomSortBook>
{
protected override void Configure(ISortInputTypeDescriptor<CustomSortBook> descriptor)
{
descriptor.BindFieldsExplicitly();
descriptor.Field(b => b.Title);
descriptor.Field("metadata")
.Type<CustomSortMetadataInputType>()
.Extend()
.OnBeforeCreate(d => d.Handler = new CustomSortFieldHandler());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"data": {
"customSortBooks": [
{
"id": 1,
"title": "Zebra"
},
{
"id": 2,
"title": "Apple"
}
]
}
}
Loading