Skip to content

Commit c450fc5

Browse files
authored
[Fusion] Satisfiability validation (#8294)
1 parent 9d2f9d6 commit c450fc5

20 files changed

+3269
-23
lines changed
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
using System.Collections;
2+
using System.Diagnostics.CodeAnalysis;
3+
using HotChocolate.Types;
4+
using HotChocolate.Types.Mutable;
5+
6+
namespace HotChocolate.Fusion.Collections;
7+
8+
internal sealed class SatisfiabilityPath : IEnumerable<SatisfiabilityPathItem>
9+
{
10+
private readonly Stack<SatisfiabilityPathItem> _stack = [];
11+
private readonly HashSet<SatisfiabilityPathItem> _hashSet = [];
12+
13+
public bool Contains(SatisfiabilityPathItem item)
14+
{
15+
return _hashSet.Contains(item);
16+
}
17+
18+
public int Count => _stack.Count;
19+
20+
public bool Push(SatisfiabilityPathItem item)
21+
{
22+
if (_hashSet.Contains(item))
23+
{
24+
return false;
25+
}
26+
27+
_stack.Push(item);
28+
_hashSet.Add(item);
29+
30+
return true;
31+
}
32+
33+
public SatisfiabilityPathItem Pop()
34+
{
35+
if (_stack.Count == 0)
36+
{
37+
throw new InvalidOperationException("Stack is empty.");
38+
}
39+
40+
var item = _stack.Pop();
41+
_hashSet.Remove(item);
42+
43+
return item;
44+
}
45+
46+
public SatisfiabilityPathItem Peek()
47+
{
48+
if (_stack.Count == 0)
49+
{
50+
throw new InvalidOperationException("Stack is empty.");
51+
}
52+
53+
return _stack.Peek();
54+
}
55+
56+
public bool TryPeek([MaybeNullWhen(false)] out SatisfiabilityPathItem item)
57+
{
58+
if (_stack.Count == 0)
59+
{
60+
item = null;
61+
return false;
62+
}
63+
64+
item = _stack.Peek();
65+
return true;
66+
}
67+
68+
public void Clear()
69+
{
70+
_stack.Clear();
71+
_hashSet.Clear();
72+
}
73+
74+
public IEnumerator<SatisfiabilityPathItem> GetEnumerator()
75+
{
76+
return _stack.GetEnumerator();
77+
}
78+
79+
IEnumerator IEnumerable.GetEnumerator()
80+
{
81+
return GetEnumerator();
82+
}
83+
84+
public override string ToString()
85+
{
86+
return string.Join(" -> ", _stack.Reverse());
87+
}
88+
}
89+
90+
internal sealed record SatisfiabilityPathItem(
91+
MutableOutputFieldDefinition Field,
92+
MutableComplexTypeDefinition Type,
93+
string SchemaName)
94+
{
95+
public ITypeDefinition FieldType { get; } = Field.Type.AsTypeDefinition();
96+
97+
private readonly int _hashCode = HashCode.Combine(Field, Type, SchemaName);
98+
99+
public override string ToString()
100+
{
101+
return $"{SchemaName}:{Type.Name}.{Field.Name}<{FieldType.Name}>";
102+
}
103+
104+
public override int GetHashCode() => _hashCode;
105+
}

src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Errors/ErrorHelper.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ namespace HotChocolate.Fusion.Errors;
44

55
internal static class ErrorHelper
66
{
7+
public static CompositionError SatisfiabilityValidationFailed()
8+
=> new(ErrorHelper_SatisfiabilityValidationFailed);
9+
710
public static CompositionError SourceSchemaParsingFailed()
811
=> new(ErrorHelper_SourceSchemaParsingFailed);
912

src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Extensions/MutableObjectTypeDefinitionExtensions.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
using HotChocolate.Types;
12
using HotChocolate.Types.Mutable;
3+
using static HotChocolate.Fusion.WellKnownArgumentNames;
24
using static HotChocolate.Fusion.WellKnownDirectiveNames;
35

46
namespace HotChocolate.Fusion.Extensions;
@@ -10,6 +12,55 @@ public static void ApplyShareableDirective(this MutableObjectTypeDefinition type
1012
type.Directives.Add(new Directive(new MutableDirectiveDefinition(Shareable)));
1113
}
1214

15+
public static bool ExistsInSchema(this MutableObjectTypeDefinition type, string schemaName)
16+
{
17+
return type.Directives.AsEnumerable().Any(
18+
d => d.Name == FusionType && (string)d.Arguments[Schema].Value! == schemaName);
19+
}
20+
21+
public static IEnumerable<IDirective> GetFusionLookupDirectives(
22+
this MutableObjectTypeDefinition type,
23+
string schemaName,
24+
IEnumerable<MutableUnionTypeDefinition> unionTypes)
25+
{
26+
var lookupDirectives =
27+
type.Directives
28+
.AsEnumerable()
29+
.Where(
30+
d =>
31+
d.Name == FusionLookup
32+
&& (string)d.Arguments[Schema].Value! == schemaName)
33+
.ToList();
34+
35+
// To use an abstract lookup, the type must exist in the source schema.
36+
if (type.ExistsInSchema(schemaName))
37+
{
38+
// Interface lookups.
39+
foreach (var interfaceType in type.Implements)
40+
{
41+
lookupDirectives.AddRange(
42+
interfaceType.Directives
43+
.AsEnumerable()
44+
.Where(d =>
45+
d.Name == FusionLookup
46+
&& (string)d.Arguments[Schema].Value! == schemaName));
47+
}
48+
49+
// Union lookups.
50+
foreach (var unionType in unionTypes)
51+
{
52+
lookupDirectives.AddRange(
53+
unionType.Directives
54+
.AsEnumerable()
55+
.Where(d =>
56+
d.Name == FusionLookup
57+
&& (string)d.Arguments[Schema].Value! == schemaName));
58+
}
59+
}
60+
61+
return lookupDirectives;
62+
}
63+
1364
public static bool HasInternalDirective(this MutableObjectTypeDefinition type)
1465
{
1566
return type.Directives.ContainsName(Internal);
Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,83 @@
1-
using System.Collections.Immutable;
2-
using HotChocolate.Types;
1+
using HotChocolate.Language;
32
using HotChocolate.Types.Mutable;
43
using static HotChocolate.Fusion.WellKnownArgumentNames;
54
using static HotChocolate.Fusion.WellKnownDirectiveNames;
5+
using static HotChocolate.Language.Utf8GraphQLParser.Syntax;
66

77
namespace HotChocolate.Fusion.Extensions;
88

99
internal static class MutableOutputFieldDefinitionExtensions
1010
{
1111
public static void ApplyLookupDirective(this MutableOutputFieldDefinition field)
12-
=> field.Directives.Add(new Directive(new MutableDirectiveDefinition(Lookup)));
12+
{
13+
field.Directives.Add(new Directive(new MutableDirectiveDefinition(Lookup)));
14+
}
1315

14-
public static ImmutableArray<string> GetSchemaNames(this IOutputFieldDefinition field)
16+
public static string? GetFusionFieldProvides(
17+
this MutableOutputFieldDefinition field,
18+
string schemaName)
1519
{
16-
var fusionFieldDirectives = field.Directives.AsEnumerable().Where(d => d.Name == FusionField);
17-
return [.. fusionFieldDirectives.Select(d => (string)d.Arguments[Schema].Value!)];
20+
var fusionFieldDirective =
21+
field.Directives.AsEnumerable().FirstOrDefault(
22+
d =>
23+
d.Name == FusionField
24+
&& (string)d.Arguments[Schema].Value! == schemaName);
25+
26+
if (fusionFieldDirective?.Arguments.TryGetValue(
27+
WellKnownArgumentNames.Provides,
28+
out var provides) == true)
29+
{
30+
return (string?)provides.Value;
31+
}
32+
33+
return null;
34+
}
35+
36+
public static SelectionSetNode? GetFusionRequiresRequirements(
37+
this MutableOutputFieldDefinition field,
38+
string schemaName)
39+
{
40+
var fusionRequiresDirective =
41+
field.Directives
42+
.AsEnumerable()
43+
.FirstOrDefault(
44+
d =>
45+
d.Name == FusionRequires
46+
&& (string)d.Arguments[Schema].Value! == schemaName);
47+
48+
if (fusionRequiresDirective is null)
49+
{
50+
return null;
51+
}
52+
53+
var requirements = (string)fusionRequiresDirective.Arguments[Requirements].Value!;
54+
55+
return ParseSelectionSet($"{{ {requirements} }}");
1856
}
1957

2058
public static bool HasInternalDirective(this MutableOutputFieldDefinition type)
21-
=> type.Directives.ContainsName(Internal);
59+
{
60+
return type.Directives.ContainsName(Internal);
61+
}
62+
63+
public static bool IsPartial(this MutableOutputFieldDefinition field, string schemaName)
64+
{
65+
var fusionFieldDirective =
66+
field.Directives.AsEnumerable().FirstOrDefault(
67+
d =>
68+
d.Name == FusionField
69+
&& (string)d.Arguments[Schema].Value! == schemaName);
70+
71+
if (fusionFieldDirective is null)
72+
{
73+
return false;
74+
}
75+
76+
if (fusionFieldDirective.Arguments.TryGetValue(Partial, out var partial))
77+
{
78+
return (bool)partial.Value!;
79+
}
80+
81+
return false;
82+
}
2283
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System.Collections.Immutable;
2+
using HotChocolate.Types;
3+
using static HotChocolate.Fusion.WellKnownArgumentNames;
4+
using static HotChocolate.Fusion.WellKnownDirectiveNames;
5+
6+
namespace HotChocolate.Fusion.Extensions;
7+
8+
internal static class OutputFieldDefinitionExtensions
9+
{
10+
public static ImmutableArray<string> GetSchemaNames(
11+
this IOutputFieldDefinition field,
12+
string? first = null)
13+
{
14+
var fusionFieldDirectives =
15+
field.Directives.AsEnumerable().Where(d => d.Name == FusionField);
16+
17+
var schemaNames =
18+
fusionFieldDirectives.Select(d => (string)d.Arguments[Schema].Value!).ToList();
19+
20+
if (first is not null && schemaNames.Contains(first))
21+
{
22+
schemaNames = schemaNames.Where(n => n != first).Prepend(first).ToList();
23+
}
24+
25+
return [.. schemaNames];
26+
}
27+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using HotChocolate.Fusion.Collections;
2+
using HotChocolate.Fusion.Validators;
3+
using HotChocolate.Types.Mutable;
4+
using static HotChocolate.Language.Utf8GraphQLParser.Syntax;
5+
6+
namespace HotChocolate.Fusion.Extensions;
7+
8+
internal static class SatisfiabilityPathItemExtensions
9+
{
10+
/// <summary>
11+
/// Determines if the <see cref="SatisfiabilityPathItem"/> provides the given field on the given
12+
/// type and schema.
13+
/// </summary>
14+
public static bool Provides(
15+
this SatisfiabilityPathItem item,
16+
MutableOutputFieldDefinition field,
17+
MutableObjectTypeDefinition type,
18+
string schemaName,
19+
MutableSchemaDefinition schema)
20+
{
21+
if (item.SchemaName != schemaName)
22+
{
23+
return false;
24+
}
25+
26+
var selectionSetText = item.Field.GetFusionFieldProvides(item.SchemaName);
27+
28+
if (selectionSetText is null)
29+
{
30+
return false;
31+
}
32+
33+
var selectionSet = ParseSelectionSet($"{{ {selectionSetText} }}");
34+
var validator = new FieldInSelectionSetValidator(schema);
35+
36+
return validator.Validate(selectionSet, item.FieldType, field, type);
37+
}
38+
}

0 commit comments

Comments
 (0)