Skip to content

Commit a558f1b

Browse files
authored
Introduced Fusion Configuration Rewriter (#6311)
1 parent 41979c5 commit a558f1b

12 files changed

+452
-69
lines changed
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
using HotChocolate.Fusion.Metadata;
2+
using HotChocolate.Language;
3+
4+
namespace HotChocolate.Fusion.Configuration;
5+
6+
/// <summary>
7+
/// This base class allows to rewrite the gateway configuration before it is applied.
8+
/// </summary>
9+
public abstract class ConfigurationRewriter : IConfigurationRewriter
10+
{
11+
/// <summary>
12+
/// Rewrites the gateway configuration.
13+
/// </summary>
14+
/// <param name="configuration">
15+
/// The gateway configuration.
16+
/// </param>
17+
/// <param name="cancellationToken">
18+
/// The cancellation token.
19+
/// </param>
20+
/// <returns>
21+
/// Returns the rewritten gateway configuration.
22+
/// </returns>
23+
public virtual async ValueTask<DocumentNode> RewriteAsync(
24+
DocumentNode configuration,
25+
CancellationToken cancellationToken = default)
26+
{
27+
if (configuration == null)
28+
{
29+
throw new ArgumentNullException(nameof(configuration));
30+
}
31+
32+
var config = FusionGraphConfiguration.Load(configuration);
33+
34+
SchemaDefinitionNode? schemaDefinitionNode = null;
35+
List<DirectiveNode>? schemaDirectives = null;
36+
37+
foreach (var client in config.HttpClients)
38+
{
39+
var rewritten = await RewriteAsync(client, cancellationToken).ConfigureAwait(false);;
40+
41+
if (!ReferenceEquals(rewritten, client))
42+
{
43+
var arguments = new List<ArgumentNode>();
44+
arguments.Add(new ArgumentNode(FusionDirectiveArgumentNames.ClientNameArg, rewritten.ClientName));
45+
arguments.Add(new ArgumentNode(FusionDirectiveArgumentNames.SubgraphArg, rewritten.SubgraphName));
46+
arguments.Add(new ArgumentNode(FusionDirectiveArgumentNames.BaseAddressArg, rewritten.EndpointUri.ToString()));
47+
Replace(client.SyntaxNode!, client.SyntaxNode!.WithArguments(arguments));
48+
}
49+
}
50+
51+
foreach (var client in config.WebSocketClients)
52+
{
53+
var rewritten = await RewriteAsync(client, cancellationToken).ConfigureAwait(false);
54+
55+
if (!ReferenceEquals(rewritten, client))
56+
{
57+
var arguments = new List<ArgumentNode>();
58+
arguments.Add(new ArgumentNode(FusionDirectiveArgumentNames.ClientNameArg, rewritten.ClientName));
59+
arguments.Add(new ArgumentNode(FusionDirectiveArgumentNames.SubgraphArg, rewritten.SubgraphName));
60+
arguments.Add(new ArgumentNode(FusionDirectiveArgumentNames.BaseAddressArg, rewritten.EndpointUri.ToString()));
61+
Replace(client.SyntaxNode!, client.SyntaxNode!.WithArguments(arguments));
62+
}
63+
}
64+
65+
return configuration;
66+
67+
void Replace(DirectiveNode currentDirective, DirectiveNode newDirective)
68+
{
69+
if (schemaDirectives is null)
70+
{
71+
if (schemaDefinitionNode is null)
72+
{
73+
schemaDefinitionNode = configuration.Definitions.OfType<SchemaDefinitionNode>().First();
74+
}
75+
76+
var definitions = configuration.Definitions.ToList();
77+
schemaDirectives = schemaDefinitionNode.Directives.ToList();
78+
79+
var definitionIndex = definitions.IndexOf(schemaDefinitionNode);
80+
schemaDefinitionNode = schemaDefinitionNode.WithDirectives(schemaDirectives);
81+
definitions[definitionIndex] = schemaDefinitionNode;
82+
configuration = configuration.WithDefinitions(definitions);
83+
}
84+
85+
var index = schemaDirectives.IndexOf(currentDirective);
86+
schemaDirectives[index] = newDirective;
87+
}
88+
}
89+
90+
/// <summary>
91+
/// Rewrites the HTTP client configuration of a subgraph.
92+
/// </summary>
93+
/// <param name="configuration">
94+
/// The HTTP client configuration.
95+
/// </param>
96+
/// <param name="cancellationToken">
97+
/// The cancellation token.
98+
/// </param>
99+
/// <returns>
100+
/// Returns the rewritten HTTP client configuration.
101+
/// </returns>
102+
protected virtual ValueTask<HttpClientConfiguration> RewriteAsync(
103+
HttpClientConfiguration configuration,
104+
CancellationToken cancellationToken)
105+
=> new(configuration);
106+
107+
/// <summary>
108+
/// Rewrites the WebSocket client configuration of a subgraph.
109+
/// </summary>
110+
/// <param name="configuration">
111+
/// The WebSocket client configuration.
112+
/// </param>
113+
/// <param name="cancellationToken">
114+
/// The cancellation token.
115+
/// </param>
116+
/// <returns>
117+
/// Returns the rewritten WebSocket client configuration.
118+
/// </returns>
119+
protected virtual ValueTask<WebSocketClientConfiguration> RewriteAsync(
120+
WebSocketClientConfiguration configuration,
121+
CancellationToken cancellationToken)
122+
=> new(configuration);
123+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using HotChocolate.Language;
2+
3+
namespace HotChocolate.Fusion.Configuration;
4+
5+
/// <summary>
6+
/// This interface allows to rewrite the gateway configuration before it is applied.
7+
/// </summary>
8+
public interface IConfigurationRewriter
9+
{
10+
/// <summary>
11+
/// Rewrites the gateway configuration.
12+
/// </summary>
13+
/// <param name="configuration">
14+
/// The gateway configuration.
15+
/// </param>
16+
/// <param name="cancellationToken">
17+
/// The cancellation token.
18+
/// </param>
19+
/// <returns>
20+
/// Returns the rewritten gateway configuration.
21+
/// </returns>
22+
ValueTask<DocumentNode> RewriteAsync(
23+
DocumentNode configuration,
24+
CancellationToken cancellationToken = default);
25+
}

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

Lines changed: 0 additions & 35 deletions
This file was deleted.

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,7 @@ public GatewayConfigurationFileObserver(string filename)
2020
}
2121

2222
public IDisposable Subscribe(IObserver<GatewayConfiguration> observer)
23-
{
24-
return new FileConfigurationSession(observer, _filename);
25-
}
23+
=> new FileConfigurationSession(observer, _filename);
2624

2725
private sealed class FileConfigurationSession : IDisposable
2826
{

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

Lines changed: 0 additions & 13 deletions
This file was deleted.

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

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,32 @@
11
using HotChocolate;
22
using HotChocolate.Execution.Configuration;
33
using HotChocolate.Fusion;
4+
using HotChocolate.Fusion.Configuration;
45
using HotChocolate.Fusion.Metadata;
56
using HotChocolate.Language;
7+
using HotChocolate.Utilities;
8+
using static System.Threading.Tasks.TaskCreationOptions;
69

710
namespace Microsoft.Extensions.DependencyInjection;
811

912
internal sealed class GatewayConfigurationTypeModule : TypeModule
1013
{
11-
private readonly TaskCompletionSource _ready = new(TaskCreationOptions.RunContinuationsAsynchronously);
14+
private readonly IEnumerable<IConfigurationRewriter> _configurationRewriters;
15+
private readonly TaskCompletionSource _ready = new(RunContinuationsAsynchronously);
1216
private DocumentNode? _configuration;
1317

14-
public GatewayConfigurationTypeModule(IObservable<GatewayConfiguration> configuration)
18+
public GatewayConfigurationTypeModule(
19+
IObservable<GatewayConfiguration> configuration,
20+
IEnumerable<IConfigurationRewriter> configurationRewriters)
1521
{
22+
if (configuration == null)
23+
{
24+
throw new ArgumentNullException(nameof(configuration));
25+
}
26+
27+
_configurationRewriters = configurationRewriters ??
28+
throw new ArgumentNullException(nameof(configurationRewriters));
29+
1630
configuration.Subscribe(
1731
config =>
1832
{
@@ -24,7 +38,9 @@ public GatewayConfigurationTypeModule(IObservable<GatewayConfiguration> configur
2438
() => _ready.TrySetCanceled());
2539
}
2640

27-
internal override async ValueTask ConfigureAsync(ConfigurationContext context, CancellationToken cancellationToken)
41+
internal override async ValueTask ConfigureAsync(
42+
ConfigurationContext context,
43+
CancellationToken cancellationToken)
2844
{
2945
await _ready.Task.WaitAsync(cancellationToken).ConfigureAwait(false);
3046

@@ -33,11 +49,24 @@ internal override async ValueTask ConfigureAsync(ConfigurationContext context, C
3349
throw ThrowHelper.UnableToLoadConfiguration();
3450
}
3551

52+
var config = _configuration;
53+
54+
foreach (var rewriter in _configurationRewriters)
55+
{
56+
config = await rewriter.RewriteAsync(config, cancellationToken)
57+
.ConfigureAwait(false);
58+
}
59+
60+
ApplyConfiguration(context.SchemaBuilder, config);
61+
}
62+
63+
private void ApplyConfiguration(ISchemaBuilder schemaBuilder, DocumentNode config)
64+
{
3665
var rewriter = new FusionGraphConfigurationToSchemaRewriter();
37-
var fusionGraphConfig = FusionGraphConfiguration.Load(_configuration);
38-
var schemaDoc = rewriter.Rewrite(_configuration);
66+
var fusionGraphConfig = FusionGraphConfiguration.Load(config);
67+
var schemaDoc = rewriter.Rewrite(config);
3968

40-
context.SchemaBuilder
69+
schemaBuilder
4170
.AddDocument(schemaDoc)
4271
.SetFusionGraphConfig(fusionGraphConfig);
4372
}

src/HotChocolate/Fusion/src/Core/HotChocolate.Fusion.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,5 @@
5050
<PackageReference Include="System.Reactive" Version="5.0.0" />
5151
</ItemGroup>
5252

53+
5354
</Project>

src/HotChocolate/Fusion/src/Core/Metadata/FusionGraphConfigurationReader.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ private HttpClientConfiguration ReadHttpClientConfig(
180180
name = subgraph;
181181
}
182182

183-
return new HttpClientConfiguration(name, subgraph, new Uri(baseAddress));
183+
return new HttpClientConfiguration(name, subgraph, new Uri(baseAddress), directiveNode);
184184

185185
static void OptionalArgs(HashSet<string> assert)
186186
{
@@ -239,7 +239,7 @@ private WebSocketClientConfiguration ReadWebSocketClientConfig(
239239
name = subgraph;
240240
}
241241

242-
return new WebSocketClientConfiguration(name, subgraph, new Uri(baseAddress));
242+
return new WebSocketClientConfiguration(name, subgraph, new Uri(baseAddress), directiveNode);
243243

244244
static void OptionalArgs(HashSet<string> assert)
245245
{

src/HotChocolate/Fusion/src/Core/Metadata/HttpClientConfiguration.cs

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
using HotChocolate.Language;
2+
13
namespace HotChocolate.Fusion.Metadata;
24

35
/// <summary>
46
/// Represents the configuration of a GraphQL over HTTP client.
57
/// </summary>
6-
public sealed class HttpClientConfiguration : IGraphQLClientConfiguration
8+
public sealed record HttpClientConfiguration : IGraphQLClientConfiguration
79
{
810
/// <summary>
911
/// Initializes a new instance of <see cref="HttpClientConfiguration"/>.
@@ -17,25 +19,38 @@ public sealed class HttpClientConfiguration : IGraphQLClientConfiguration
1719
/// <param name="endpointUri">
1820
/// The base address of the GraphQL over HTTP endpoint.
1921
/// </param>
20-
public HttpClientConfiguration(string clientName, string subgraphName, Uri endpointUri)
22+
/// <param name="syntaxNode">
23+
/// The configuration syntax node.
24+
/// </param>
25+
public HttpClientConfiguration(
26+
string clientName,
27+
string subgraphName,
28+
Uri endpointUri,
29+
DirectiveNode? syntaxNode = null)
2130
{
2231
ClientName = clientName;
2332
SubgraphName = subgraphName;
2433
EndpointUri = endpointUri;
34+
SyntaxNode = syntaxNode;
2535
}
2636

2737
/// <summary>
2838
/// Gets the name of the client.
2939
/// </summary>
30-
public string ClientName { get; }
40+
public string ClientName { get; init; }
3141

3242
/// <summary>
3343
/// Gets the name of the subgraph that the client is connecting to.
3444
/// </summary>
35-
public string SubgraphName { get; }
45+
public string SubgraphName { get; init; }
3646

3747
/// <summary>
3848
/// Gets the URI of the GraphQL over HTTP endpoint.
3949
/// </summary>
40-
public Uri EndpointUri { get; }
50+
public Uri EndpointUri { get; init; }
51+
52+
/// <summary>
53+
/// Gets the configuration syntax node.
54+
/// </summary>
55+
public DirectiveNode? SyntaxNode { get; init; }
4156
}

0 commit comments

Comments
 (0)