Skip to content

Commit 5f9ea2e

Browse files
[Fusion] Properly handle @deprecated (#8753)
1 parent b81c373 commit 5f9ea2e

File tree

9 files changed

+413
-14
lines changed

9 files changed

+413
-14
lines changed

src/HotChocolate/Fusion-vnext/src/Fusion.Aspire/CompositionHelper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ await archive.GetSourceSchemaNamesAsync(cancellationToken),
6666

6767
var compositionLog = new CompositionLog();
6868
var schemaComposer = new SchemaComposer(
69-
sourceSchemaMap.Values.OrderBy(t => t.Name).Select(t => t.Schema),
69+
sourceSchemaMap.Values.Select(t => t.Schema),
7070
new SchemaComposerOptions
7171
{
7272
EnableGlobalObjectIdentification = settings.EnableGlobalObjectIdentification

src/HotChocolate/Fusion-vnext/src/Fusion.Composition/SourceSchemaMerger.cs

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,11 +166,17 @@ private static MutableInputFieldDefinition MergeArguments(
166166
var type = MostRestrictiveType(typeA, typeB);
167167
var description = argumentA.Description ?? argumentB.Description;
168168
var defaultValue = argumentA.DefaultValue ?? argumentB.DefaultValue;
169+
var isDeprecated = argumentA.IsDeprecated || argumentB.IsDeprecated;
170+
var deprecationReason = isDeprecated
171+
? argumentA.DeprecationReason ?? argumentB.DeprecationReason
172+
: null;
169173

170174
return new MutableInputFieldDefinition(argumentA.Name, type.ExpectInputType())
171175
{
172176
DefaultValue = defaultValue,
173-
Description = description
177+
Description = description,
178+
IsDeprecated = isDeprecated,
179+
DeprecationReason = deprecationReason
174180
};
175181
}
176182

@@ -270,13 +276,31 @@ private MutableEnumValue MergeEnumValues(ImmutableArray<EnumValueInfo> enumValue
270276
var firstValue = enumValueGroup[0].EnumValue;
271277
var valueName = firstValue.Name;
272278
var description = firstValue.Description;
279+
var isDeprecated = firstValue.IsDeprecated;
280+
var deprecationReason = firstValue.DeprecationReason;
273281

274282
for (var i = 1; i < enumValueGroup.Length; i++)
275283
{
276-
description ??= enumValueGroup[i].EnumValue.Description;
284+
var enumValueInfo = enumValueGroup[i];
285+
description ??= enumValueInfo.EnumValue.Description;
286+
287+
if (enumValueInfo.EnumValue.IsDeprecated && !isDeprecated)
288+
{
289+
isDeprecated = true;
290+
}
291+
292+
if (isDeprecated && string.IsNullOrEmpty(deprecationReason))
293+
{
294+
deprecationReason = enumValueInfo.EnumValue.DeprecationReason;
295+
}
277296
}
278297

279-
var enumValue = new MutableEnumValue(valueName) { Description = description };
298+
var enumValue = new MutableEnumValue(valueName)
299+
{
300+
Description = description,
301+
IsDeprecated = isDeprecated,
302+
DeprecationReason = deprecationReason
303+
};
280304

281305
AddFusionEnumValueDirectives(enumValue, enumValueGroup);
282306

@@ -356,19 +380,33 @@ private MutableInputFieldDefinition MergeInputFields(
356380
var fieldType = firstField.Type;
357381
var description = firstField.Description;
358382
var defaultValue = firstField.DefaultValue;
383+
var isDeprecated = firstField.IsDeprecated;
384+
var deprecationReason = firstField.DeprecationReason;
359385

360386
for (var i = 1; i < inputFieldGroup.Length; i++)
361387
{
362388
var inputFieldInfo = inputFieldGroup[i];
363389
fieldType = MostRestrictiveType(fieldType, inputFieldInfo.Field.Type).ExpectInputType();
364390
description ??= inputFieldInfo.Field.Description;
365391
defaultValue ??= inputFieldInfo.Field.DefaultValue;
392+
393+
if (inputFieldInfo.Field.IsDeprecated && !isDeprecated)
394+
{
395+
isDeprecated = true;
396+
}
397+
398+
if (isDeprecated && string.IsNullOrEmpty(deprecationReason))
399+
{
400+
deprecationReason = inputFieldInfo.Field.DeprecationReason;
401+
}
366402
}
367403

368404
var inputField = new MutableInputFieldDefinition(fieldName)
369405
{
370406
DefaultValue = defaultValue,
371407
Description = description,
408+
IsDeprecated = isDeprecated,
409+
DeprecationReason = deprecationReason,
372410
Type = fieldType
373411
.ReplaceNamedType(_ => GetOrCreateType(mergedSchema, fieldType))
374412
.ExpectInputType()
@@ -579,17 +617,31 @@ .. fieldGroup.Where(i => !i.Field.HasInternalDirective() && !i.IsOverridden(grou
579617
var fieldName = firstField.Name;
580618
var fieldType = firstField.Type;
581619
var description = firstField.Description;
620+
var isDeprecated = firstField.IsDeprecated;
621+
var deprecationReason = firstField.DeprecationReason;
582622

583623
for (var i = 1; i < fieldGroup.Length; i++)
584624
{
585625
var fieldInfo = fieldGroup[i];
586626
fieldType = LeastRestrictiveType(fieldType, fieldInfo.Field.Type).ExpectOutputType();
587627
description ??= fieldInfo.Field.Description;
628+
629+
if (fieldInfo.Field.IsDeprecated && !isDeprecated)
630+
{
631+
isDeprecated = true;
632+
}
633+
634+
if (isDeprecated && string.IsNullOrEmpty(deprecationReason))
635+
{
636+
deprecationReason = fieldInfo.Field.DeprecationReason;
637+
}
588638
}
589639

590640
var outputField = new MutableOutputFieldDefinition(fieldName)
591641
{
592642
Description = description,
643+
IsDeprecated = isDeprecated,
644+
DeprecationReason = deprecationReason,
593645
Type = fieldType
594646
.ReplaceNamedType(_ => GetOrCreateType(mergedSchema, fieldType))
595647
.ExpectOutputType()

src/HotChocolate/Fusion-vnext/src/Fusion.Execution.Types/Directives/DirectiveTools.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ public static IImmutableList<DirectiveNode> GetUserDirectives(
2525
continue;
2626
}
2727

28+
if (DeprecatedDirectiveParser.CanParse(directiveNode))
29+
{
30+
continue;
31+
}
32+
2833
// TODO : Remove once we have a better way to handle built-in directives.
2934
if (FusionBuiltIns.IsBuiltInDirective(directiveNode.Name.Value))
3035
{

src/HotChocolate/Fusion-vnext/test/Fusion.AspNetCore.Tests/__snapshots__/IntrospectionTests.IntrospectionQueries_IntrospectionQuery.yaml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1153,8 +1153,8 @@ response:
11531153
"ofType": null
11541154
},
11551155
"defaultValue": null,
1156-
"isDeprecated": false,
1157-
"deprecationReason": null
1156+
"isDeprecated": true,
1157+
"deprecationReason": "No longer supported"
11581158
}
11591159
],
11601160
"type": {
@@ -1198,8 +1198,8 @@ response:
11981198
}
11991199
}
12001200
},
1201-
"isDeprecated": false,
1202-
"deprecationReason": null
1201+
"isDeprecated": true,
1202+
"deprecationReason": "No longer supported"
12031203
}
12041204
],
12051205
"inputFields": null,
@@ -1508,8 +1508,8 @@ response:
15081508
"ofType": null
15091509
},
15101510
"defaultValue": null,
1511-
"isDeprecated": false,
1512-
"deprecationReason": null
1511+
"isDeprecated": true,
1512+
"deprecationReason": "No longer supported"
15131513
}
15141514
],
15151515
"interfaces": null,
@@ -1580,8 +1580,8 @@ response:
15801580
{
15811581
"name": "PHOTO",
15821582
"description": null,
1583-
"isDeprecated": false,
1584-
"deprecationReason": null
1583+
"isDeprecated": true,
1584+
"deprecationReason": "No longer supported"
15851585
},
15861586
{
15871587
"name": "STORY",

src/HotChocolate/Fusion-vnext/test/Fusion.Composition.Tests/SourceSchemaMerger.Argument.Tests.cs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,96 @@ type ProductDimension
199199
scalar DeliveryEstimates
200200
@fusion__type(schema: A)
201201
"""
202+
},
203+
// Even if an argument is only @deprecated in one source schema,
204+
// the composite argument is marked as @deprecated.
205+
{
206+
[
207+
""""
208+
# Schema A
209+
type Product {
210+
reviews(filter: String @deprecated(reason: "Some reason")): [String]
211+
}
212+
"""",
213+
"""
214+
# Schema B
215+
type Product {
216+
reviews(filter: String): [String]
217+
}
218+
"""
219+
],
220+
"""
221+
type Product
222+
@fusion__type(schema: A)
223+
@fusion__type(schema: B) {
224+
reviews(filter: String
225+
@fusion__inputField(schema: A)
226+
@fusion__inputField(schema: B)
227+
@deprecated(reason: "Some reason")): [String]
228+
@fusion__field(schema: A)
229+
@fusion__field(schema: B)
230+
}
231+
"""
232+
},
233+
// If the same argument is @deprecated in multiple source schemas,
234+
// the first non-null deprecation message is chosen.
235+
{
236+
[
237+
""""
238+
# Schema A
239+
type Product {
240+
reviews(filter: String @deprecated(reason: "Some reason")): [String]
241+
}
242+
"""",
243+
"""
244+
# Schema B
245+
type Product {
246+
reviews(filter: String @deprecated(reason: "Another reason")): [String]
247+
}
248+
"""
249+
],
250+
"""
251+
type Product
252+
@fusion__type(schema: A)
253+
@fusion__type(schema: B) {
254+
reviews(filter: String
255+
@fusion__inputField(schema: A)
256+
@fusion__inputField(schema: B)
257+
@deprecated(reason: "Some reason")): [String]
258+
@fusion__field(schema: A)
259+
@fusion__field(schema: B)
260+
}
261+
"""
262+
},
263+
// If the an argument is deprecated without a deprecation reason,
264+
// a default reason is inserted to be compatible with the latest spec.
265+
{
266+
[
267+
""""
268+
# Schema A
269+
type Product {
270+
reviews(filter: String @deprecated): [String]
271+
}
272+
"""",
273+
"""
274+
# Schema B
275+
type Product {
276+
reviews(filter: String @deprecated): [String]
277+
}
278+
"""
279+
],
280+
"""
281+
type Product
282+
@fusion__type(schema: A)
283+
@fusion__type(schema: B) {
284+
reviews(filter: String
285+
@fusion__inputField(schema: A)
286+
@fusion__inputField(schema: B)
287+
@deprecated(reason: "No longer supported.")): [String]
288+
@fusion__field(schema: A)
289+
@fusion__field(schema: B)
290+
}
291+
"""
202292
}
203293
};
204294
}

src/HotChocolate/Fusion-vnext/test/Fusion.Composition.Tests/SourceSchemaMerger.EnumValue.Tests.cs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,90 @@ enum Status
8888
@fusion__enumValue(schema: B)
8989
}
9090
"""
91+
},
92+
// Even if an enum value is only @deprecated in one source schema,
93+
// the composite enum value is marked as @deprecated.
94+
{
95+
[
96+
"""
97+
# Schema A
98+
enum Status {
99+
ACTIVE @deprecated(reason: "Some reason")
100+
}
101+
""",
102+
"""
103+
# Schema B
104+
enum Status {
105+
ACTIVE
106+
}
107+
"""
108+
],
109+
"""
110+
enum Status
111+
@fusion__type(schema: A)
112+
@fusion__type(schema: B) {
113+
ACTIVE
114+
@fusion__enumValue(schema: A)
115+
@fusion__enumValue(schema: B)
116+
@deprecated(reason: "Some reason")
117+
}
118+
"""
119+
},
120+
// If the same enum value is @deprecated in multiple source schemas,
121+
// the first non-null deprecation message is chosen.
122+
{
123+
[
124+
"""
125+
# Schema A
126+
enum Status {
127+
ACTIVE @deprecated(reason: "Some reason")
128+
}
129+
""",
130+
"""
131+
# Schema B
132+
enum Status {
133+
ACTIVE @deprecated(reason: "Another reason")
134+
}
135+
"""
136+
],
137+
"""
138+
enum Status
139+
@fusion__type(schema: A)
140+
@fusion__type(schema: B) {
141+
ACTIVE
142+
@fusion__enumValue(schema: A)
143+
@fusion__enumValue(schema: B)
144+
@deprecated(reason: "Some reason")
145+
}
146+
"""
147+
},
148+
// If the an enum value is deprecated without a deprecation reason,
149+
// a default reason is inserted to be compatible with the latest spec.
150+
{
151+
[
152+
"""
153+
# Schema A
154+
enum Status {
155+
ACTIVE @deprecated
156+
}
157+
""",
158+
"""
159+
# Schema B
160+
enum Status {
161+
ACTIVE @deprecated
162+
}
163+
"""
164+
],
165+
"""
166+
enum Status
167+
@fusion__type(schema: A)
168+
@fusion__type(schema: B) {
169+
ACTIVE
170+
@fusion__enumValue(schema: A)
171+
@fusion__enumValue(schema: B)
172+
@deprecated(reason: "No longer supported.")
173+
}
174+
"""
91175
}
92176
};
93177
}

0 commit comments

Comments
 (0)