-
-
Notifications
You must be signed in to change notification settings - Fork 725
/
ObjectField.cs
200 lines (171 loc) · 6.96 KB
/
ObjectField.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
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using HotChocolate.Configuration;
using HotChocolate.Resolvers;
using HotChocolate.Types.Descriptors.Definitions;
#nullable enable
namespace HotChocolate.Types
{
/// <summary>
/// Represents a field of an <see cref="ObjectType"/>.
/// </summary>
public class ObjectField
: OutputFieldBase<ObjectFieldDefinition>
, IObjectField
{
private static readonly FieldDelegate _empty = c =>
throw new InvalidOperationException();
private readonly List<IDirective> _executableDirectives =
new List<IDirective>();
internal ObjectField(ObjectFieldDefinition definition, bool sortArgumentsByName = false)
: base(definition, sortArgumentsByName)
{
Member = definition.Member ?? definition.ResolverMember;
Middleware = _empty;
Resolver = definition.Resolver!;
SubscribeResolver = definition.SubscribeResolver;
ExecutableDirectives = _executableDirectives.AsReadOnly();
IsIntrospectionField = definition.IsIntrospectionField;
}
/// <summary>
/// Gets the type that declares this field.
/// </summary>
public new ObjectType DeclaringType => (ObjectType)base.DeclaringType;
IObjectType IObjectField.DeclaringType => DeclaringType;
/// <summary>
/// Gets the field resolver middleware.
/// </summary>
public FieldDelegate Middleware { get; private set; }
/// <summary>
/// Gets the field resolver.
/// </summary>
public FieldResolverDelegate Resolver { get; private set; }
/// <summary>
/// Gets the subscription resolver.
/// </summary>
public SubscribeResolverDelegate? SubscribeResolver { get; }
/// <summary>
/// Gets all executable directives that are associated with this field.
/// </summary>
public IReadOnlyList<IDirective> ExecutableDirectives { get; }
/// <summary>
/// Gets the associated .net type member of this field.
/// This member can be <c>null</c>.
/// </summary>
public MemberInfo? Member { get; }
/// <summary>
/// Gets the associated resolver expression.
/// This expression can be <c>null</c>.
/// </summary>
public Expression? Expression { get; }
/// <summary>
/// Defines if this field as a introspection field.
/// </summary>
public override bool IsIntrospectionField { get; }
protected override void OnCompleteField(
ITypeCompletionContext context,
ObjectFieldDefinition definition)
{
base.OnCompleteField(context, definition);
CompleteExecutableDirectives(context);
CompleteResolver(context, definition);
}
private void CompleteExecutableDirectives(
ITypeCompletionContext context)
{
var processed = new HashSet<string>();
if (context.Type is ObjectType ot)
{
AddExecutableDirectives(processed, ot.Directives);
AddExecutableDirectives(processed, Directives);
}
}
private void AddExecutableDirectives(
ISet<string> processed,
IEnumerable<IDirective> directives)
{
foreach (IDirective directive in directives.Where(t => t.IsExecutable))
{
if (!processed.Add(directive.Name) && !directive.Type.IsRepeatable)
{
IDirective remove = _executableDirectives
.First(t => t.Name.Equals(directive.Name));
_executableDirectives.Remove(remove);
}
_executableDirectives.Add(directive);
}
}
private void CompleteResolver(
ITypeCompletionContext context,
ObjectFieldDefinition definition)
{
var isIntrospectionField = IsIntrospectionField
|| DeclaringType.IsIntrospectionType();
Resolver = definition.Resolver!;
if (!isIntrospectionField || Resolver is null)
{
// gets resolvers that were provided via type extensions,
// explicit resolver results or are provided through the
// resolver compiler.
FieldResolver resolver = context.GetResolver(definition.Name);
Resolver = GetMostSpecificResolver(context.Type.Name, Resolver, resolver)!;
}
IReadOnlySchemaOptions options = context.DescriptorContext.Options;
var skipMiddleware =
options.FieldMiddleware != FieldMiddlewareApplication.AllFields &&
isIntrospectionField;
Middleware = FieldMiddlewareCompiler.Compile(
context.GlobalComponents,
definition.MiddlewareComponents.ToArray(),
Resolver,
skipMiddleware);
if (Resolver is null && Middleware is null)
{
if (_executableDirectives.Count > 0)
{
Middleware = ctx => default;
}
else
{
context.ReportError(SchemaErrorBuilder.New()
.SetMessage(
$"The field `{context.Type.Name}.{Name}` " +
"has no resolver.")
.SetCode(ErrorCodes.Schema.NoResolver)
.SetTypeSystemObject(context.Type)
.AddSyntaxNode(definition.SyntaxNode)
.Build());
}
}
}
/// <summary>
/// Gets the most relevant overwrite of a resolver.
/// </summary>
private static FieldResolverDelegate? GetMostSpecificResolver(
NameString typeName,
FieldResolverDelegate? currentResolver,
FieldResolver? externalCompiledResolver)
{
// if there is no external compiled resolver then we will pick
// the internal resolver delegate.
if (externalCompiledResolver is null)
{
return currentResolver;
}
// if the internal resolver is null or if the external compiled
// resolver represents an explicit overwrite of the type resolver
// then we will pick the external compiled resolver.
if (currentResolver is null
|| externalCompiledResolver.TypeName.Equals(typeName))
{
return externalCompiledResolver.Resolver;
}
// in all other cases we will pick the internal resolver delegate.
return currentResolver;
}
public override string ToString() => $"{Name}:{Type.Visualize()}";
}
}