Skip to content

Commit c0192a1

Browse files
[Fusion] Added post-merge validation rule "EmptyMergedInterfaceTypeRule" (#7980)
1 parent e25f735 commit c0192a1

File tree

7 files changed

+186
-0
lines changed

7 files changed

+186
-0
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ public static class LogEntryCodes
44
{
55
public const string DisallowedInaccessible = "DISALLOWED_INACCESSIBLE";
66
public const string EmptyMergedEnumType = "EMPTY_MERGED_ENUM_TYPE";
7+
public const string EmptyMergedInterfaceType = "EMPTY_MERGED_INTERFACE_TYPE";
78
public const string EmptyMergedObjectType = "EMPTY_MERGED_OBJECT_TYPE";
89
public const string EmptyMergedUnionType = "EMPTY_MERGED_UNION_TYPE";
910
public const string EnumValuesMismatch = "ENUM_VALUES_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
@@ -113,6 +113,19 @@ public static LogEntry EmptyMergedEnumType(
113113
schema);
114114
}
115115

116+
public static LogEntry EmptyMergedInterfaceType(
117+
InterfaceTypeDefinition interfaceType,
118+
SchemaDefinition schema)
119+
{
120+
return new LogEntry(
121+
string.Format(LogEntryHelper_EmptyMergedInterfaceType, interfaceType.Name),
122+
LogEntryCodes.EmptyMergedInterfaceType,
123+
LogSeverity.Error,
124+
new SchemaCoordinate(interfaceType.Name),
125+
interfaceType,
126+
schema);
127+
}
128+
116129
public static LogEntry EmptyMergedObjectType(
117130
ObjectTypeDefinition objectType,
118131
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-Interface-Type">
12+
/// Specification
13+
/// </seealso>
14+
internal sealed class EmptyMergedInterfaceTypeRule : IEventHandler<InterfaceTypeEvent>
15+
{
16+
public void Handle(InterfaceTypeEvent @event, CompositionContext context)
17+
{
18+
var (interfaceType, schema) = @event;
19+
20+
if (interfaceType.HasInaccessibleDirective())
21+
{
22+
return;
23+
}
24+
25+
var accessibleFields = interfaceType.Fields.Where(f => !f.HasInaccessibleDirective());
26+
27+
if (!accessibleFields.Any())
28+
{
29+
context.Log.Write(EmptyMergedInterfaceType(interfaceType, schema));
30+
}
31+
}
32+
}

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
@@ -48,6 +48,9 @@
4848
<data name="LogEntryHelper_EmptyMergedEnumType" xml:space="preserve">
4949
<value>The merged enum type '{0}' is empty.</value>
5050
</data>
51+
<data name="LogEntryHelper_EmptyMergedInterfaceType" xml:space="preserve">
52+
<value>The merged interface type '{0}' is empty.</value>
53+
</data>
5154
<data name="LogEntryHelper_EmptyMergedObjectType" xml:space="preserve">
5255
<value>The merged object type '{0}' is empty.</value>
5356
</data>

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ public CompositionResult<SchemaDefinition> Compose()
121121
private static readonly ImmutableArray<object> s_postMergeRules =
122122
[
123123
new EmptyMergedEnumTypeRule(),
124+
new EmptyMergedInterfaceTypeRule(),
124125
new EmptyMergedObjectTypeRule(),
125126
new EmptyMergedUnionTypeRule()
126127
];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
using System.Collections.Immutable;
2+
using HotChocolate.Fusion.Logging;
3+
4+
namespace HotChocolate.Fusion.PostMergeValidationRules;
5+
6+
public sealed class EmptyMergedInterfaceTypeRuleTests : CompositionTestBase
7+
{
8+
private static readonly object s_rule = new EmptyMergedInterfaceTypeRule();
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_INTERFACE_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+
interface Author {
60+
name: String
61+
age: Int @inaccessible
62+
}
63+
""",
64+
"""
65+
# Schema B
66+
interface Author {
67+
age: Int
68+
registered: Boolean
69+
}
70+
"""
71+
]
72+
},
73+
// TODO: Check spec text
74+
// If the @inaccessible directive is applied to an interface type itself, the entire
75+
// merged interface type is excluded from the composite schema, and it is not required
76+
// to contain any fields.
77+
{
78+
[
79+
"""
80+
# Schema A
81+
interface Author @inaccessible {
82+
name: String
83+
age: Int
84+
}
85+
""",
86+
"""
87+
# Schema B
88+
interface Author {
89+
registered: Boolean
90+
}
91+
"""
92+
]
93+
}
94+
};
95+
}
96+
97+
public static TheoryData<string[], string[]> InvalidExamplesData()
98+
{
99+
return new TheoryData<string[], string[]>
100+
{
101+
// This example demonstrates an invalid merged interface type. In this case, "Author" is
102+
// defined in two source schemas, but all fields are marked as @inaccessible in at least
103+
// one of the source schemas, resulting in an empty merged interface type.
104+
{
105+
[
106+
"""
107+
# Schema A
108+
interface Author {
109+
name: String @inaccessible
110+
registered: Boolean
111+
}
112+
""",
113+
"""
114+
# Schema B
115+
interface Author {
116+
name: String
117+
registered: Boolean @inaccessible
118+
}
119+
"""
120+
],
121+
[
122+
"The merged interface type 'Author' is empty."
123+
]
124+
}
125+
};
126+
}
127+
}

0 commit comments

Comments
 (0)