Skip to content

Commit 775481c

Browse files
[Fusion] @semanticNonNull support (#7894)
1 parent 2b53375 commit 775481c

File tree

7 files changed

+3165
-50
lines changed

7 files changed

+3165
-50
lines changed

src/HotChocolate/Core/src/Types/SemanticNonNullTypeInterceptor.cs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
using HotChocolate.Types.Descriptors;
99
using HotChocolate.Types.Descriptors.Definitions;
1010
using HotChocolate.Types.Helpers;
11-
using HotChocolate.Types.Pagination;
1211
using HotChocolate.Types.Relay;
12+
using HotChocolate.Utilities;
1313

1414
namespace HotChocolate;
1515

@@ -346,7 +346,7 @@ private static void CheckResultForSemanticNonNullViolations(object? result, IRes
346346
{
347347
if (result is null && levels.Contains(currentLevel))
348348
{
349-
context.ReportError(CreateSemanticNonNullViolationError(path));
349+
context.ReportError(ErrorHelper.CreateSemanticNonNullViolationError(path, context.Selection));
350350
return;
351351
}
352352

@@ -367,11 +367,4 @@ private static void CheckResultForSemanticNonNullViolations(object? result, IRes
367367
}
368368
}
369369
}
370-
371-
private static IError CreateSemanticNonNullViolationError(Path path)
372-
=> ErrorBuilder.New()
373-
.SetMessage("Cannot return null for semantic non-null field.")
374-
.SetCode(ErrorCodes.Execution.SemanticNonNullViolation)
375-
.SetPath(path)
376-
.Build();
377370
}

src/HotChocolate/Core/src/Types/Utilities/ErrorHelper.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Globalization;
2+
using HotChocolate.Execution.Processing;
23
using HotChocolate.Language;
34
using HotChocolate.Properties;
45
using HotChocolate.Types;
@@ -538,4 +539,12 @@ public static ISchemaError DuplicateFieldName(
538539
.SetTypeSystemObject(type)
539540
.Build();
540541
}
542+
543+
public static IError CreateSemanticNonNullViolationError(Path path, ISelection selection)
544+
=> ErrorBuilder.New()
545+
.SetMessage("Cannot return null for semantic non-null field.")
546+
.SetCode(ErrorCodes.Execution.SemanticNonNullViolation)
547+
.AddLocation(selection.SyntaxNode)
548+
.SetPath(path)
549+
.Build();
541550
}

src/HotChocolate/Fusion/src/Core/DependencyInjection/FusionRequestExecutorBuilderExtensions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ public static FusionGatewayBuilder AddFusionGatewayServer(
6262
.UseField(next => next)
6363
.AddOperationCompilerOptimizer<OperationQueryPlanCompiler>()
6464
.AddOperationCompilerOptimizer<FieldFlagsOptimizer>()
65+
.AddOperationCompilerOptimizer<SemanticNonNullOptimizer>()
6566
.AddConvention<INamingConventions>(_ => new DefaultNamingConventions())
6667
.ModifyCostOptions(o => o.ApplyCostDefaults = false)
6768
.Configure(

src/HotChocolate/Fusion/src/Core/Execution/ExecutionUtils.cs

Lines changed: 121 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Text.Json;
55
using HotChocolate.Execution.Processing;
66
using HotChocolate.Fusion.Execution.Nodes;
7+
using HotChocolate.Fusion.Execution.Pipeline;
78
using HotChocolate.Fusion.Metadata;
89
using HotChocolate.Fusion.Planning;
910
using HotChocolate.Fusion.Utilities;
@@ -12,6 +13,7 @@
1213
using HotChocolate.Types;
1314
using HotChocolate.Utilities;
1415
using static HotChocolate.Execution.Processing.Selection;
16+
using ErrorHelper = HotChocolate.Utilities.ErrorHelper;
1517
using IType = HotChocolate.Types.IType;
1618
using ObjectType = HotChocolate.Types.ObjectType;
1719

@@ -41,7 +43,8 @@ private static void ComposeResult(
4143
SelectionSet selectionSet,
4244
SelectionData[] selectionSetData,
4345
ObjectResult selectionSetResult,
44-
bool partialResult = false)
46+
bool partialResult = false,
47+
int level = 0)
4548
{
4649
if (selectionSetResult.IsInvalidated)
4750
{
@@ -69,14 +72,19 @@ private static void ComposeResult(
6972

7073
if (!field.IsIntrospectionField)
7174
{
72-
var nullable = selection.TypeKind is not TypeKind.NonNull;
73-
var namedType = selectionType.NamedType();
75+
var isSemanticNonNull = IsSemanticNonNull(selection, level);
76+
var nullable = selectionType.IsNullableType();
77+
var nullableType = selectionType.NullableType();
7478

7579
if (!data.HasValue)
7680
{
7781
if (!partialResult)
7882
{
79-
if (!nullable)
83+
if (isSemanticNonNull)
84+
{
85+
AddSemanticNonNullViolation(context.Result, selection, selectionSetResult, responseIndex);
86+
}
87+
else if (!nullable)
8088
{
8189
PropagateNullValues(context.Result, selection, selectionSetResult, responseIndex);
8290
break;
@@ -85,14 +93,21 @@ private static void ComposeResult(
8593
result.Set(responseName, null, nullable);
8694
}
8795
}
88-
else if (namedType.IsType(TypeKind.Scalar))
96+
else if (nullableType.IsType(TypeKind.Scalar))
8997
{
9098
var value = data.Single.Element;
9199

92-
if (value.ValueKind is JsonValueKind.Null or JsonValueKind.Undefined && !nullable)
100+
if (value.ValueKind is JsonValueKind.Null or JsonValueKind.Undefined)
93101
{
94-
PropagateNullValues(context.Result, selection, selectionSetResult, responseIndex);
95-
break;
102+
if (isSemanticNonNull)
103+
{
104+
AddSemanticNonNullViolation(context.Result, selection, selectionSetResult, responseIndex);
105+
}
106+
else if (!nullable)
107+
{
108+
PropagateNullValues(context.Result, selection, selectionSetResult, responseIndex);
109+
break;
110+
}
96111
}
97112

98113
result.Set(responseName, value, nullable);
@@ -105,15 +120,21 @@ private static void ComposeResult(
105120
result.Set(responseName, reformattedId, nullable);
106121
}
107122
}
108-
else if (namedType.IsType(TypeKind.Enum))
123+
else if (nullableType.IsType(TypeKind.Enum))
109124
{
110-
// we might need to map the enum value!
111125
var value = data.Single.Element;
112126

113-
if (value.ValueKind is JsonValueKind.Null or JsonValueKind.Undefined && !nullable)
127+
if (value.ValueKind is JsonValueKind.Null or JsonValueKind.Undefined)
114128
{
115-
PropagateNullValues(context.Result, selection, selectionSetResult, responseIndex);
116-
break;
129+
if (isSemanticNonNull)
130+
{
131+
AddSemanticNonNullViolation(context.Result, selection, selectionSetResult, responseIndex);
132+
}
133+
else if (!nullable)
134+
{
135+
PropagateNullValues(context.Result, selection, selectionSetResult, responseIndex);
136+
break;
137+
}
117138
}
118139

119140
result.Set(responseName, value, nullable);
@@ -122,7 +143,7 @@ private static void ComposeResult(
122143
{
123144
if (!result.IsInitialized)
124145
{
125-
// we add a placeholder here so if the ComposeObject propagates an error
146+
// we add a placeholder here so if ComposeObject propagates an error
126147
// there is a value here.
127148
result.Set(responseName, null, nullable);
128149

@@ -131,12 +152,20 @@ private static void ComposeResult(
131152
selectionSetResult,
132153
responseIndex,
133154
selection,
134-
data);
155+
data,
156+
level);
135157

136-
if (value is null && !nullable)
158+
if (value is null)
137159
{
138-
PropagateNullValues(context.Result, selection, selectionSetResult, responseIndex);
139-
break;
160+
if (isSemanticNonNull)
161+
{
162+
AddSemanticNonNullViolation(context.Result, selection, selectionSetResult, responseIndex);
163+
}
164+
else if (!nullable)
165+
{
166+
PropagateNullValues(context.Result, selection, selectionSetResult, responseIndex);
167+
break;
168+
}
140169
}
141170

142171
result.Set(responseName, value, nullable);
@@ -146,18 +175,30 @@ private static void ComposeResult(
146175
{
147176
if (!result.IsInitialized)
148177
{
178+
// we add a placeholder here so if ComposeList propagates an error
179+
// there is a value here.
180+
result.Set(responseName, null, nullable);
181+
149182
var value = ComposeList(
150183
context,
151184
selectionSetResult,
152185
responseIndex,
153186
selection,
154187
data,
155-
selectionType);
188+
selectionType,
189+
level + 1);
156190

157-
if (value is null && !nullable)
191+
if (value is null)
158192
{
159-
PropagateNullValues(context.Result, selection, selectionSetResult, responseIndex);
160-
break;
193+
if (isSemanticNonNull)
194+
{
195+
AddSemanticNonNullViolation(context.Result, selection, selectionSetResult, responseIndex);
196+
}
197+
else if (!nullable)
198+
{
199+
PropagateNullValues(context.Result, selection, selectionSetResult, responseIndex);
200+
break;
201+
}
161202
}
162203

163204
result.Set(responseName, value, nullable);
@@ -191,7 +232,8 @@ private static void ComposeResult(
191232
int parentIndex,
192233
Selection selection,
193234
SelectionData selectionData,
194-
IType type)
235+
IType type,
236+
int level)
195237
{
196238
if (selectionData.IsNull())
197239
{
@@ -206,14 +248,15 @@ private static void ComposeResult(
206248
var index = 0;
207249
var elementType = type.ElementType();
208250
var nullable = elementType.IsNullableType();
251+
var isSemanticNonNull = IsSemanticNonNull(selection, level);
209252
var result = context.Result.RentList(json.GetArrayLength());
210253

211254
result.IsNullable = nullable;
212255
result.SetParent(parent, parentIndex);
213256

214257
foreach (var item in json.EnumerateArray())
215258
{
216-
// we add a placeholder here so if the ComposeElement propagates an error
259+
// we add a placeholder here so if ComposeElement propagates an error
217260
// there is a value here.
218261
result.AddUnsafe(null);
219262

@@ -223,12 +266,20 @@ private static void ComposeResult(
223266
index,
224267
selection,
225268
new SelectionData(new JsonResult(schemaName, item)),
226-
elementType);
269+
elementType,
270+
level);
227271

228-
if (!nullable && element is null)
272+
if (element is null)
229273
{
230-
PropagateNullValues(context.Result, selection, result, index);
231-
return null;
274+
if (isSemanticNonNull)
275+
{
276+
AddSemanticNonNullViolation(context.Result, selection, result, index);
277+
}
278+
else if (!nullable)
279+
{
280+
PropagateNullValues(context.Result, selection, result, index);
281+
break;
282+
}
232283
}
233284

234285
result.SetUnsafe(index++, element);
@@ -248,16 +299,17 @@ private static void ComposeResult(
248299
int parentIndex,
249300
Selection selection,
250301
SelectionData selectionData,
251-
IType valueType)
302+
IType valueType,
303+
int level)
252304
{
253-
var namedType = valueType.NamedType();
305+
var nullableType = valueType.NullableType();
254306

255307
if (!selectionData.HasValue)
256308
{
257309
return null;
258310
}
259311

260-
if (namedType.IsType(TypeKind.Scalar))
312+
if (nullableType.IsType(TypeKind.Scalar))
261313
{
262314
var value = selectionData.Single.Element;
263315

@@ -276,7 +328,7 @@ private static void ComposeResult(
276328
return value;
277329
}
278330

279-
if (namedType.IsType(TypeKind.Enum))
331+
if (nullableType.IsType(TypeKind.Enum))
280332
{
281333
var value = selectionData.Single.Element;
282334

@@ -288,17 +340,18 @@ private static void ComposeResult(
288340
return value;
289341
}
290342

291-
return TypeExtensions.IsCompositeType(valueType)
292-
? ComposeObject(context, parent, parentIndex, selection, selectionData)
293-
: ComposeList(context, parent, parentIndex, selection, selectionData, valueType);
343+
return nullableType.IsCompositeType()
344+
? ComposeObject(context, parent, parentIndex, selection, selectionData, 0)
345+
: ComposeList(context, parent, parentIndex, selection, selectionData, valueType, level + 1);
294346
}
295347

296348
private static ObjectResult? ComposeObject(
297349
FusionExecutionContext context,
298350
ResultData parent,
299351
int parentIndex,
300352
ISelection selection,
301-
SelectionData selectionData)
353+
SelectionData selectionData,
354+
int level)
302355
{
303356
if (selectionData.IsNull())
304357
{
@@ -330,13 +383,13 @@ private static void ComposeResult(
330383

331384
var childSelectionResults = new SelectionData[selectionCount];
332385
ExtractSelectionResults(selectionData, selectionSet, childSelectionResults);
333-
ComposeResult(context, selectionSet, childSelectionResults, result, true);
386+
ComposeResult(context, selectionSet, childSelectionResults, result, true, level);
334387
}
335388
else
336389
{
337390
var childSelectionResults = new SelectionData[selectionCount];
338391
ExtractSelectionResults(selectionData, selectionSet, childSelectionResults);
339-
ComposeResult(context, selectionSet, childSelectionResults, result);
392+
ComposeResult(context, selectionSet, childSelectionResults, result, false, level);
340393
}
341394

342395
return result.IsInvalidated ? null : result;
@@ -716,6 +769,17 @@ public static void ExtractVariables(
716769
}
717770
}
718771

772+
private static void AddSemanticNonNullViolation(
773+
ResultBuilder resultBuilder,
774+
Selection selection,
775+
ResultData selectionSetResult,
776+
int responseIndex)
777+
{
778+
var path = PathHelper.CreatePathFromContext(selection, selectionSetResult, responseIndex);
779+
var error = ErrorHelper.CreateSemanticNonNullViolationError(path, selection);
780+
resultBuilder.AddError(error);
781+
}
782+
719783
private static void PropagateNullValues(
720784
ResultBuilder resultBuilder,
721785
Selection selection,
@@ -727,6 +791,25 @@ private static void PropagateNullValues(
727791
ValueCompletion.PropagateNullValues(selectionSetResult);
728792
}
729793

794+
private static readonly CustomOptionsFlags[] _levelOptions =
795+
[
796+
CustomOptionsFlags.Option5,
797+
CustomOptionsFlags.Option6,
798+
CustomOptionsFlags.Option7
799+
];
800+
801+
private static bool IsSemanticNonNull(Selection selection, int level)
802+
{
803+
if (level >= SemanticNonNullOptimizer.MaxLevels)
804+
{
805+
return true;
806+
}
807+
808+
var optionForLevel = _levelOptions[level];
809+
810+
return selection.CustomOptions.HasFlag(optionForLevel);
811+
}
812+
730813
private sealed class ErrorPathContext
731814
{
732815
public string Current { get; set; } = string.Empty;

0 commit comments

Comments
 (0)