Skip to content

Commit 117238d

Browse files
[Fusion] Added post-merge validation rule "EmptyMergedEnumTypeRule" (#7977)
1 parent 01398f6 commit 117238d

File tree

9 files changed

+193
-0
lines changed

9 files changed

+193
-0
lines changed

src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Events/SchemaEvents.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ internal record DirectiveArgumentEvent(
1010
DirectiveDefinition Directive,
1111
SchemaDefinition Schema) : IEvent;
1212

13+
internal record EnumTypeEvent(
14+
EnumTypeDefinition EnumType,
15+
SchemaDefinition Schema) : IEvent;
16+
1317
internal record FieldArgumentEvent(
1418
InputFieldDefinition Argument,
1519
OutputFieldDefinition Field,

src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LogEntryCodes.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ namespace HotChocolate.Fusion.Logging;
33
public static class LogEntryCodes
44
{
55
public const string DisallowedInaccessible = "DISALLOWED_INACCESSIBLE";
6+
public const string EmptyMergedEnumType = "EMPTY_MERGED_ENUM_TYPE";
67
public const string EmptyMergedObjectType = "EMPTY_MERGED_OBJECT_TYPE";
78
public const string EnumValuesMismatch = "ENUM_VALUES_MISMATCH";
89
public const string ExternalArgumentDefaultMismatch = "EXTERNAL_ARGUMENT_DEFAULT_MISMATCH";

src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LogEntryHelper.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,19 @@ public static LogEntry DisallowedInaccessibleDirectiveArgument(
100100
schema);
101101
}
102102

103+
public static LogEntry EmptyMergedEnumType(
104+
EnumTypeDefinition enumType,
105+
SchemaDefinition schema)
106+
{
107+
return new LogEntry(
108+
string.Format(LogEntryHelper_EmptyMergedEnumType, enumType.Name),
109+
LogEntryCodes.EmptyMergedEnumType,
110+
LogSeverity.Error,
111+
new SchemaCoordinate(enumType.Name),
112+
enumType,
113+
schema);
114+
}
115+
103116
public static LogEntry EmptyMergedObjectType(
104117
ObjectTypeDefinition objectType,
105118
SchemaDefinition schema)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using HotChocolate.Fusion.Events;
2+
using HotChocolate.Fusion.Events.Contracts;
3+
using HotChocolate.Fusion.Extensions;
4+
using static HotChocolate.Fusion.Logging.LogEntryHelper;
5+
6+
namespace HotChocolate.Fusion.PostMergeValidationRules;
7+
8+
/// <summary>
9+
/// TODO: Summary
10+
/// </summary>
11+
/// <seealso href="https://graphql.github.io/composite-schemas-spec/draft/#sec-Empty-Merged-Enum-Type">
12+
/// Specification
13+
/// </seealso>
14+
internal sealed class EmptyMergedEnumTypeRule : IEventHandler<EnumTypeEvent>
15+
{
16+
public void Handle(EnumTypeEvent @event, CompositionContext context)
17+
{
18+
var (enumType, schema) = @event;
19+
20+
if (enumType.HasInaccessibleDirective())
21+
{
22+
return;
23+
}
24+
25+
var accessibleValues = enumType.Values.Where(v => !v.HasInaccessibleDirective());
26+
27+
if (!accessibleValues.Any())
28+
{
29+
context.Log.Write(EmptyMergedEnumType(enumType, schema));
30+
}
31+
}
32+
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ private void PublishEvents()
3333
{
3434
switch (type)
3535
{
36+
case EnumTypeDefinition enumType:
37+
PublishEvent(new EnumTypeEvent(enumType, mergedSchema), context);
38+
break;
39+
3640
case InputObjectTypeDefinition inputType:
3741
PublishEvent(new InputTypeEvent(inputType, mergedSchema), context);
3842

src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.Designer.cs

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@
4545
<data name="LogEntryHelper_DisallowedInaccessibleDirectiveArgument" xml:space="preserve">
4646
<value>The built-in directive argument '{0}' in schema '{1}' is not accessible.</value>
4747
</data>
48+
<data name="LogEntryHelper_EmptyMergedEnumType" xml:space="preserve">
49+
<value>The merged enum type '{0}' is empty.</value>
50+
</data>
4851
<data name="LogEntryHelper_EmptyMergedObjectType" xml:space="preserve">
4952
<value>The merged object type '{0}' is empty.</value>
5053
</data>

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ public CompositionResult<SchemaDefinition> Compose()
120120

121121
private static readonly ImmutableArray<object> s_postMergeRules =
122122
[
123+
new EmptyMergedEnumTypeRule(),
123124
new EmptyMergedObjectTypeRule()
124125
];
125126
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
using System.Collections.Immutable;
2+
using HotChocolate.Fusion.Logging;
3+
4+
namespace HotChocolate.Fusion.PostMergeValidationRules;
5+
6+
public sealed class EmptyMergedEnumTypeRuleTests : CompositionTestBase
7+
{
8+
private static readonly object s_rule = new EmptyMergedEnumTypeRule();
9+
private static readonly ImmutableArray<object> s_rules = [s_rule];
10+
private readonly CompositionLog _log = new();
11+
12+
[Theory]
13+
[MemberData(nameof(ValidExamplesData))]
14+
public void Examples_Valid(string[] sdl)
15+
{
16+
// arrange
17+
var schemas = CreateSchemaDefinitions(sdl);
18+
var merger = new SourceSchemaMerger(schemas);
19+
var mergeResult = merger.Merge();
20+
var validator = new PostMergeValidator(mergeResult.Value, s_rules, schemas, _log);
21+
22+
// act
23+
var result = validator.Validate();
24+
25+
// assert
26+
Assert.True(result.IsSuccess);
27+
Assert.True(_log.IsEmpty);
28+
}
29+
30+
[Theory]
31+
[MemberData(nameof(InvalidExamplesData))]
32+
public void Examples_Invalid(string[] sdl, string[] errorMessages)
33+
{
34+
// arrange
35+
var schemas = CreateSchemaDefinitions(sdl);
36+
var merger = new SourceSchemaMerger(schemas);
37+
var mergeResult = merger.Merge();
38+
var validator = new PostMergeValidator(mergeResult.Value, s_rules, schemas, _log);
39+
40+
// act
41+
var result = validator.Validate();
42+
43+
// assert
44+
Assert.True(result.IsFailure);
45+
Assert.Equal(errorMessages, _log.Select(e => e.Message).ToArray());
46+
Assert.True(_log.All(e => e.Code == "EMPTY_MERGED_ENUM_TYPE"));
47+
Assert.True(_log.All(e => e.Severity == LogSeverity.Error));
48+
}
49+
50+
public static TheoryData<string[]> ValidExamplesData()
51+
{
52+
return new TheoryData<string[]>
53+
{
54+
// TODO: Use examples from spec
55+
{
56+
[
57+
"""
58+
# Schema A
59+
enum Genre {
60+
FANTASY
61+
SCIENCE_FICTION @inaccessible
62+
}
63+
""",
64+
"""
65+
# Schema B
66+
enum Genre {
67+
FANTASY
68+
}
69+
"""
70+
]
71+
},
72+
// TODO: Check spec text
73+
// If the @inaccessible directive is applied to an enum type itself, the entire merged
74+
// enum type is excluded from the composite schema, and it is not required to contain
75+
// any values.
76+
{
77+
[
78+
"""
79+
# Schema A
80+
enum Genre @inaccessible {
81+
FANTASY
82+
SCIENCE_FICTION
83+
}
84+
""",
85+
"""
86+
# Schema B
87+
enum Genre {
88+
FANTASY
89+
}
90+
"""
91+
]
92+
}
93+
};
94+
}
95+
96+
public static TheoryData<string[], string[]> InvalidExamplesData()
97+
{
98+
return new TheoryData<string[], string[]>
99+
{
100+
// This example demonstrates an invalid merged enum type. In this case, "Genre" is
101+
// defined in two source schemas, but all values are marked as @inaccessible in at least
102+
// one of the source schemas, resulting in an empty merged enum type.
103+
{
104+
[
105+
"""
106+
# Schema A
107+
enum Genre {
108+
FANTASY
109+
SCIENCE_FICTION @inaccessible
110+
}
111+
""",
112+
"""
113+
# Schema B
114+
enum Genre {
115+
FANTASY @inaccessible
116+
SCIENCE_FICTION
117+
}
118+
"""
119+
],
120+
[
121+
"The merged enum type 'Genre' is empty."
122+
]
123+
}
124+
};
125+
}
126+
}

0 commit comments

Comments
 (0)