-
-
Notifications
You must be signed in to change notification settings - Fork 725
/
QueryableSortProvider.cs
133 lines (117 loc) · 4.94 KB
/
QueryableSortProvider.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using HotChocolate.Language;
using HotChocolate.Resolvers;
using HotChocolate.Types;
namespace HotChocolate.Data.Sorting.Expressions
{
public class QueryableSortProvider
: SortProvider<QueryableSortContext>
{
public const string ContextArgumentNameKey = "SortArgumentName";
public const string ContextVisitSortArgumentKey = nameof(VisitSortArgument);
public const string SkipSortingKey = "SkipSorting";
public QueryableSortProvider()
{
}
public QueryableSortProvider(
Action<ISortProviderDescriptor<QueryableSortContext>> configure)
: base(configure)
{
}
protected virtual SortVisitor<QueryableSortContext, QueryableSortOperation> Visitor { get; }
= new SortVisitor<QueryableSortContext, QueryableSortOperation>();
public override FieldMiddleware CreateExecutor<TEntityType>(NameString argumentName)
{
return next => context => ExecuteAsync(next, context);
async ValueTask ExecuteAsync(
FieldDelegate next,
IMiddlewareContext context)
{
// first we let the pipeline run and produce a result.
await next(context).ConfigureAwait(false);
// next we get the sort argument.
IInputField argument = context.Field.Arguments[argumentName];
IValueNode sort = context.ArgumentLiteral<IValueNode>(argumentName);
// if no sort is defined we can stop here and yield back control.
if (sort.IsNull() ||
(context.LocalContextData.TryGetValue(
SkipSortingKey,
out object? skipObject) &&
skipObject is bool skip &&
skip))
{
return;
}
if (argument.Type is ListType lt &&
lt.ElementType is NonNullType nn &&
nn.NamedType() is ISortInputType sortInput &&
context.Field.ContextData.TryGetValue(
ContextVisitSortArgumentKey,
out object? executorObj) &&
executorObj is VisitSortArgument executor)
{
var inMemory =
context.Result is QueryableExecutable<TEntityType> executable &&
executable.InMemory ||
context.Result is not IQueryable ||
context.Result is EnumerableQuery;
QueryableSortContext visitorContext = executor(
sort,
sortInput,
inMemory);
// compile expression tree
if (visitorContext.Errors.Count > 0)
{
context.Result = Array.Empty<TEntityType>();
foreach (IError error in visitorContext.Errors)
{
context.ReportError(error.WithPath(context.Path));
}
}
else
{
context.Result = context.Result switch
{
IQueryable<TEntityType> q => visitorContext.Sort(q),
IEnumerable<TEntityType> e => visitorContext.Sort(e.AsQueryable()),
QueryableExecutable<TEntityType> ex =>
ex.WithSource(visitorContext.Sort(ex.Source)),
_ => context.Result
};
}
}
}
}
public override void ConfigureField(
NameString argumentName,
IObjectFieldDescriptor descriptor)
{
QueryableSortContext VisitSortArgumentExecutor(
IValueNode valueNode,
ISortInputType filterInput,
bool inMemory)
{
var visitorContext = new QueryableSortContext(
filterInput,
inMemory);
// rewrite GraphQL input object into expression tree.
Visitor.Visit(valueNode, visitorContext);
return visitorContext;
}
descriptor.ConfigureContextData(
contextData =>
{
contextData[ContextVisitSortArgumentKey] =
(VisitSortArgument)VisitSortArgumentExecutor;
contextData[ContextArgumentNameKey] = argumentName;
});
}
}
public delegate QueryableSortContext VisitSortArgument(
IValueNode filterValueNode,
ISortInputType filterInputType,
bool inMemory);
}