-
-
Notifications
You must be signed in to change notification settings - Fork 722
/
PagingHelper.cs
201 lines (175 loc) · 7.06 KB
/
PagingHelper.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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using HotChocolate.Configuration;
using HotChocolate.Internal;
using HotChocolate.Resolvers;
using HotChocolate.Types.Descriptors;
using HotChocolate.Types.Descriptors.Definitions;
using HotChocolate.Utilities;
using static HotChocolate.WellKnownMiddleware;
#nullable enable
namespace HotChocolate.Types.Pagination;
public static class PagingHelper
{
public static IObjectFieldDescriptor UsePaging(
IObjectFieldDescriptor descriptor,
Type? entityType,
GetPagingProvider resolvePagingProvider,
PagingOptions options)
{
if (descriptor is null)
{
throw new ArgumentNullException(nameof(descriptor));
}
FieldMiddlewareDefinition placeholder = new(_ => _ => default, key: Paging);
var definition = descriptor.Extend().Definition;
definition.MiddlewareDefinitions.Add(placeholder);
definition.Configurations.Add(
new CompleteConfiguration<ObjectFieldDefinition>(
(c, d) => ApplyConfiguration(
c,
d,
entityType,
options.ProviderName,
resolvePagingProvider,
options,
placeholder),
definition,
ApplyConfigurationOn.BeforeCompletion));
return descriptor;
}
private static void ApplyConfiguration(
ITypeCompletionContext context,
ObjectFieldDefinition definition,
Type? entityType,
string? name,
GetPagingProvider resolvePagingProvider,
PagingOptions options,
FieldMiddlewareDefinition placeholder)
{
options = context.GetSettings(options);
entityType ??= context.GetType<IOutputType>(definition.Type!).ToRuntimeType();
var source = GetSourceType(context.TypeInspector, definition, entityType);
var pagingProvider = resolvePagingProvider(context.Services, source, name);
var pagingHandler = pagingProvider.CreateHandler(source, options);
var middleware = CreateMiddleware(pagingHandler);
var index = definition.MiddlewareDefinitions.IndexOf(placeholder);
definition.MiddlewareDefinitions[index] = new(middleware, key: Paging);
}
private static IExtendedType GetSourceType(
ITypeInspector typeInspector,
ObjectFieldDefinition definition,
Type entityType)
{
// if an explicit result type is defined we will type it since it expresses the
// intend.
if (definition.ResultType is not null)
{
return typeInspector.GetType(definition.ResultType);
}
// Otherwise we will look at specified members and extract the return type.
var member = definition.ResolverMember ?? definition.Member;
if (member is not null)
{
return typeInspector.GetReturnType(member, true);
}
// if we were not able to resolve the source type we will assume that it is
// an enumerable of the entity type.
return typeInspector.GetType(typeof(IEnumerable<>).MakeGenericType(entityType));
}
private static FieldMiddleware CreateMiddleware(
IPagingHandler handler) =>
FieldClassMiddlewareFactory.Create(
typeof(PagingMiddleware),
(typeof(IPagingHandler), handler));
public static IExtendedType GetSchemaType(
IDescriptorContext context,
MemberInfo? member,
Type? type = null)
{
var typeInspector = context.TypeInspector;
if (type is null &&
member is not null &&
typeInspector.GetOutputReturnTypeRef(member) is ExtendedTypeReference r &&
typeInspector.TryCreateTypeInfo(r.Type, out var typeInfo))
{
// if the member has already associated a schema type we will just take it.
// Since we want the entity element we are going to take
// the element type of the list or array as our entity type.
if (r.Type.IsSchemaType && r.Type.IsArrayOrList)
{
return r.Type.ElementType!;
}
// if the member type is unknown we will try to infer it by extracting
// the named type component from it and running the type inference.
// It might be that we either are unable to infer or get the wrong type
// in special cases. In the case we are getting it wrong the user has
// to explicitly bind the type.
if (context.TryInferSchemaType(
r.WithType(typeInspector.GetType(typeInfo.NamedType)),
out var schemaTypeRefs) &&
schemaTypeRefs is { Length:> 0 } &&
schemaTypeRefs[0] is ExtendedTypeReference schemaTypeRef)
{
// if we are able to infer the type we will reconstruct its structure so that
// we can correctly extract from it the element type with the correct
// nullability information.
var current = schemaTypeRef.Type.Type;
foreach (var component in typeInfo.Components.Reverse().Skip(1))
{
if (component.Kind == TypeComponentKind.NonNull)
{
current = typeof(NonNullType<>).MakeGenericType(current);
}
else if (component.Kind == TypeComponentKind.List)
{
current = typeof(ListType<>).MakeGenericType(current);
}
}
if (typeInspector.GetType(current) is { IsArrayOrList: true } schemaType)
{
return schemaType.ElementType!;
}
}
}
if (type is null || !typeof(IType).IsAssignableFrom(type))
{
throw ThrowHelper.UsePagingAttribute_NodeTypeUnknown(member);
}
return typeInspector.GetType(type);
}
public static bool TryGetNamedType(
ITypeInspector typeInspector,
MemberInfo? member,
[NotNullWhen(true)] out Type? namedType)
{
if (member is not null &&
typeInspector.GetReturnType(member) is { } returnType &&
typeInspector.TryCreateTypeInfo(returnType, out var typeInfo))
{
namedType = typeInfo.NamedType;
return true;
}
namedType = null;
return false;
}
public static PagingOptions GetSettings(
this ITypeCompletionContext context,
PagingOptions options) =>
context.DescriptorContext.GetSettings(options);
public static PagingOptions GetSettings(
this IDescriptorContext context,
PagingOptions options)
{
options = options.Copy();
if (context.ContextData.TryGetValue(typeof(PagingOptions).FullName!, out var o) &&
o is PagingOptions global)
{
options.Merge(global);
}
return options;
}
}