Skip to content

Commit f268379

Browse files
authored
Ensure Fusion by default has a single client config. (#6370)
1 parent 6448b70 commit f268379

37 files changed

+408
-175
lines changed

.build/Build.Environment.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,5 @@ partial class Build
3232
AbsolutePath EmptyAzf12Proj => RootDirectory / "templates" / "v12" / "function" / "HotChocolate.Template.AzureFunctions.csproj";
3333
AbsolutePath EmptyAzfUp12Proj => RootDirectory / "templates" / "v12" / "function-isolated" / "HotChocolate.Template.AzureFunctions.Isolated.csproj";
3434
AbsolutePath Gateway13Proj => RootDirectory / "templates" / "v12" / "gateway" / "HotChocolate.Template.Gateway.csproj";
35+
AbsolutePath GatewayManaged13Proj => RootDirectory / "templates" / "v12" / "gateway-bcp" / "HotChocolate.Template.Gateway.csproj";
3536
}

src/HotChocolate/Fusion/src/Abstractions/FusionGraphPackage.cs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Text.Json;
66
using HotChocolate.Fusion.Composition;
77
using HotChocolate.Language;
8+
using HotChocolate.Language.Utilities;
89
using static HotChocolate.Fusion.FusionAbstractionResources;
910
using static HotChocolate.Fusion.FusionGraphPackageConstants;
1011
using static HotChocolate.Language.Utf8GraphQLParser;
@@ -23,6 +24,13 @@ namespace HotChocolate.Fusion;
2324
/// </summary>
2425
public sealed class FusionGraphPackage : IDisposable, IAsyncDisposable
2526
{
27+
private static readonly SyntaxSerializerOptions _syntaxSerializerOptions =
28+
new()
29+
{
30+
Indented = true,
31+
MaxDirectivesPerLine = 0
32+
};
33+
2634
private readonly Package _package;
2735

2836
private FusionGraphPackage(Package package)
@@ -205,7 +213,7 @@ public Task<JsonDocument> GetFusionGraphSettingsAsync(
205213
var part = _package.GetPart(relationship.TargetUri);
206214
return ReadJsonPartAsync(part, cancellationToken);
207215
}
208-
216+
209217
public Task SetFusionGraphSettingsAsync(
210218
JsonDocument document,
211219
CancellationToken cancellationToken = default)
@@ -467,7 +475,7 @@ private async Task WriteSchemaPartAsync(
467475
var part = _package.CreatePart(uri, SchemaMediaType);
468476

469477
await using var stream = part.GetStream(FileMode.Create);
470-
var sourceText = Encoding.UTF8.GetBytes(document.ToString(true));
478+
var sourceText = Encoding.UTF8.GetBytes(document.ToString(_syntaxSerializerOptions));
471479
await stream.WriteAsync(sourceText, ct);
472480

473481
_package.CreateRelationship(part.Uri, TargetMode.Internal, relKind, relId);
@@ -493,7 +501,7 @@ private async Task WriteJsonPartAsync(
493501
var uri = PackUriHelper.CreatePartUri(new Uri(fileName, UriKind.Relative));
494502
var part = _package.CreatePart(uri, JsonMediaType);
495503

496-
var options = new JsonWriterOptions { Indented = true, MaxDepth = 16 };
504+
var options = new JsonWriterOptions { Indented = true, MaxDepth = 16 };
497505
await using var stream = part.GetStream(FileMode.Create);
498506
await using var writer = new Utf8JsonWriter(stream, options);
499507
document.WriteTo(writer);
@@ -514,7 +522,7 @@ private async Task<SubgraphConfiguration> ReadSubgraphConfigurationAsync(
514522
return new SubgraphConfiguration(
515523
config.Name,
516524
schema.ToString(true),
517-
extensions.Select(t => t.ToString(true)).ToArray(),
525+
extensions.Select(t => t.ToString(_syntaxSerializerOptions)).ToArray(),
518526
config.Clients);
519527
}
520528

@@ -595,7 +603,7 @@ private async Task WriteSubgraphSchemaPartAsync(
595603
var part = _package.CreatePart(uri, SchemaMediaType);
596604

597605
await using var stream = part.GetStream(FileMode.Create);
598-
var sourceText = Encoding.UTF8.GetBytes(document.ToString(true));
606+
var sourceText = Encoding.UTF8.GetBytes(document.ToString(_syntaxSerializerOptions));
599607
await stream.WriteAsync(sourceText, ct);
600608

601609
root.CreateRelationship(part.Uri, TargetMode.Internal, SchemaKind, SchemaId);
@@ -615,7 +623,7 @@ private async Task WriteSubgraphExtensionPartsAsync(
615623
var part = _package.CreatePart(uri, SchemaMediaType);
616624

617625
await using var stream = part.GetStream(FileMode.Create);
618-
var sourceText = Encoding.UTF8.GetBytes(extension.ToString(true));
626+
var sourceText = Encoding.UTF8.GetBytes(extension.ToString(_syntaxSerializerOptions));
619627
await stream.WriteAsync(sourceText, ct);
620628

621629
root.CreateRelationship(part.Uri, TargetMode.Internal, ExtensionKind);

src/HotChocolate/Fusion/src/CommandLine/Commands/ComposeCommand.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,11 @@ private static FusionFeatureCollection CreateFeatures(
207207
{
208208
var features = new List<IFusionFeature>();
209209

210+
features.Add(new TransportFeature
211+
{
212+
DefaultClientName = settings.Transport.DefaultClientName
213+
});
214+
210215
if (settings.NodeField.Enabled)
211216
{
212217
features.Add(FusionFeatures.NodeField);
@@ -266,6 +271,7 @@ private class PackageSettings
266271
private Feature? _reEncodeIds;
267272
private Feature? _nodeField;
268273
private TagDirective? _tagDirective;
274+
private Transport? _transport;
269275

270276
[JsonPropertyName("fusionTypePrefix")]
271277
[JsonPropertyOrder(10)]
@@ -275,6 +281,12 @@ private class PackageSettings
275281
[JsonPropertyOrder(11)]
276282
public bool FusionTypeSelf { get; set; }
277283

284+
public Transport Transport
285+
{
286+
get => _transport ??= new();
287+
set => _transport = value;
288+
}
289+
278290
[JsonPropertyName("nodeField")]
279291
[JsonPropertyOrder(12)]
280292
public Feature NodeField
@@ -323,4 +335,11 @@ public string[] Exclude
323335
set => _exclude = value;
324336
}
325337
}
338+
339+
public sealed class Transport
340+
{
341+
[JsonPropertyName("defaultClientName")]
342+
[JsonPropertyOrder(10)]
343+
public string? DefaultClientName { get; set; } = "Fusion";
344+
}
326345
}

src/HotChocolate/Fusion/src/CommandLine/Helpers/PackageHelper.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Text.Json;
66
using HotChocolate.Fusion.Composition;
77
using HotChocolate.Language;
8+
using HotChocolate.Language.Utilities;
89
using static System.IO.Packaging.PackUriHelper;
910
using static System.IO.Path;
1011
using static System.UriKind;
@@ -18,6 +19,13 @@ internal static class PackageHelper
1819
private const string _schemaExtensionKind = "urn:graphql:schema-extensions";
1920
private const string _subgraphConfigKind = "urn:hotchocolate:fusion:subgraph-config";
2021
private const string _subgraphConfigId = "subgraph-config";
22+
23+
private static readonly SyntaxSerializerOptions _serializerOptions =
24+
new()
25+
{
26+
Indented = true,
27+
MaxDirectivesPerLine = 0
28+
};
2129

2230
public static async Task CreateSubgraphPackageAsync(
2331
string packageFile,
@@ -98,8 +106,8 @@ public static async Task<SubgraphConfiguration> ReadSubgraphPackageAsync(
98106

99107
return new SubgraphConfiguration(
100108
subgraphConfig.Name,
101-
schema.ToString(true),
102-
extensions.Select(t => t.ToString(true)).ToArray(),
109+
schema.ToString(_serializerOptions),
110+
extensions.Select(t => t.ToString(_serializerOptions)).ToArray(),
103111
subgraphConfig.Clients);
104112
}
105113

@@ -281,7 +289,7 @@ private static async Task AddSchemaToPackageAsync(
281289

282290
await using var stream = part.GetStream(FileMode.Create);
283291
await using var writer = new StreamWriter(stream, Encoding.UTF8);
284-
await writer.WriteAsync(schema.ToString(true));
292+
await writer.WriteAsync(schema.ToString(_serializerOptions));
285293

286294
package.CreateRelationship(part.Uri, TargetMode.Internal, _schemaKind, _schemaId);
287295
}
@@ -311,7 +319,7 @@ private static async Task AddSchemaExtensionsToPackage(
311319

312320
await using var stream = part.GetStream(FileMode.Create);
313321
await using var writer = new StreamWriter(stream, Encoding.UTF8);
314-
await writer.WriteAsync(extension.ToString(true));
322+
await writer.WriteAsync(extension.ToString(_serializerOptions));
315323

316324
package.CreateRelationship(part.Uri, TargetMode.Internal, _schemaExtensionKind);
317325
}

src/HotChocolate/Fusion/src/Composition/Features/FusionFeatureCollectionExtensions.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,19 @@ public static IReadOnlySet<string> GetExcludedTags(this FusionFeatureCollection
4545
=> features.TryGetFeature<TagDirectiveFeature>(out var feature)
4646
? feature.Excluded
4747
: _empty;
48+
49+
/// <summary>
50+
/// Gets the default client configuration name that shall be used for
51+
/// transport clients if no client name was specified.
52+
/// </summary>
53+
/// <param name="features">
54+
/// The feature collection.
55+
/// </param>
56+
/// <returns>
57+
/// The default client configuration name.
58+
/// </returns>
59+
public static string? GetDefaultClientName(this FusionFeatureCollection features)
60+
=> features.TryGetFeature<TransportFeature>(out var feature)
61+
? feature.DefaultClientName
62+
: null;
4863
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
namespace HotChocolate.Fusion.Composition.Features;
2+
3+
/// <summary>
4+
/// Specifies transport defaults for the fusion graph.
5+
/// </summary>
6+
public sealed class TransportFeature : IFusionFeature
7+
{
8+
/// <summary>
9+
/// Gets or sets the default client name that is used
10+
/// when no explicit client name was specified for a subgraph.
11+
/// </summary>
12+
public string? DefaultClientName { get; set; } = "Fusion";
13+
}

src/HotChocolate/Fusion/src/Composition/FusionTypes.cs

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -310,11 +310,17 @@ private DirectiveType RegisterNodeDirectiveType(string name, ScalarType typeName
310310
return directiveType;
311311
}
312312

313-
public Directive CreateHttpDirective(string subgraphName, Uri baseAddress)
314-
=> new Directive(
315-
HttpClient,
316-
new Argument(SubgraphArg, subgraphName),
317-
new Argument(BaseAddressArg, baseAddress.ToString()));
313+
public Directive CreateHttpDirective(string subgraphName, string? clientName, Uri baseAddress)
314+
=> clientName is null
315+
? new Directive(
316+
HttpClient,
317+
new Argument(SubgraphArg, subgraphName),
318+
new Argument(BaseAddressArg, baseAddress.ToString()))
319+
: new Directive(
320+
HttpClient,
321+
new Argument(SubgraphArg, subgraphName),
322+
new Argument(ClientNameArg, clientName),
323+
new Argument(BaseAddressArg, baseAddress.ToString()));
318324

319325
private DirectiveType RegisterHttpDirectiveType(
320326
string name,
@@ -324,17 +330,24 @@ private DirectiveType RegisterHttpDirectiveType(
324330
var directiveType = new DirectiveType(name);
325331
directiveType.Locations = DirectiveLocation.FieldDefinition;
326332
directiveType.Arguments.Add(new InputField(SubgraphArg, new NonNullType(typeName)));
333+
directiveType.Arguments.Add(new InputField(ClientNameArg, typeName));
327334
directiveType.Arguments.Add(new InputField(BaseAddressArg, uri));
328335
directiveType.ContextData.Add(WellKnownContextData.IsFusionType, true);
329336
_fusionGraph.DirectiveTypes.Add(directiveType);
330337
return directiveType;
331338
}
332339

333-
public Directive CreateWebSocketDirective(string subgraphName, Uri baseAddress)
334-
=> new Directive(
335-
WebSocketClient,
336-
new Argument(SubgraphArg, subgraphName),
337-
new Argument(BaseAddressArg, baseAddress.ToString()));
340+
public Directive CreateWebSocketDirective(string subgraphName, string? clientName, Uri baseAddress)
341+
=> clientName is null
342+
? new Directive(
343+
WebSocketClient,
344+
new Argument(SubgraphArg, subgraphName),
345+
new Argument(BaseAddressArg, baseAddress.ToString()))
346+
: new Directive(
347+
WebSocketClient,
348+
new Argument(SubgraphArg, subgraphName),
349+
new Argument(ClientNameArg, clientName),
350+
new Argument(BaseAddressArg, baseAddress.ToString()));
338351

339352
private DirectiveType RegisterWebSocketDirectiveType(
340353
string name,
@@ -344,6 +357,7 @@ private DirectiveType RegisterWebSocketDirectiveType(
344357
var directiveType = new DirectiveType(name);
345358
directiveType.Locations = DirectiveLocation.FieldDefinition;
346359
directiveType.Arguments.Add(new InputField(SubgraphArg, new NonNullType(typeName)));
360+
directiveType.Arguments.Add(new InputField(ClientNameArg, typeName));
347361
directiveType.Arguments.Add(new InputField(BaseAddressArg, uri));
348362
directiveType.ContextData.Add(WellKnownContextData.IsFusionType, true);
349363
_fusionGraph.DirectiveTypes.Add(directiveType);
@@ -383,4 +397,4 @@ private DirectiveType RegisterFusionDirectiveType(
383397

384398
return directiveType;
385399
}
386-
}
400+
}

src/HotChocolate/Fusion/src/Composition/Pipeline/RegisterClientsMiddleware.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using HotChocolate.Fusion.Composition.Features;
2+
13
namespace HotChocolate.Fusion.Composition.Pipeline;
24

35
/// <summary>
@@ -8,6 +10,8 @@ internal sealed class RegisterClientsMiddleware : IMergeMiddleware
810
/// <inheritdoc />
911
public async ValueTask InvokeAsync(CompositionContext context, MergeDelegate next)
1012
{
13+
var defaultClientName = context.Features.GetDefaultClientName();
14+
1115
foreach (var configuration in context.Configurations)
1216
{
1317
foreach (var client in configuration.Clients)
@@ -18,13 +22,15 @@ public async ValueTask InvokeAsync(CompositionContext context, MergeDelegate nex
1822
context.FusionGraph.Directives.Add(
1923
context.FusionTypes.CreateHttpDirective(
2024
configuration.Name,
25+
httpClient.ClientName ?? defaultClientName,
2126
httpClient.BaseAddress));
2227
break;
2328

2429
case WebSocketClientConfiguration webSocketClient:
2530
context.FusionGraph.Directives.Add(
2631
context.FusionTypes.CreateWebSocketDirective(
2732
configuration.Name,
33+
webSocketClient.ClientName ?? defaultClientName,
2834
webSocketClient.BaseAddress));
2935
break;
3036

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using HotChocolate.Language.Visitors;
33
using HotChocolate.Types.Introspection;
44
using HotChocolate.Utilities;
5+
using Microsoft.Extensions.DependencyInjection;
56
using static HotChocolate.Fusion.FusionDirectiveArgumentNames;
67
using static HotChocolate.Fusion.FusionResources;
78
using static HotChocolate.Fusion.ThrowHelper;

src/HotChocolate/Fusion/test/CommandLine.Tests/__snapshots__/ComposeCommandTests.Compose_Fusion_Graph.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ scalar DateTime
4545

4646
Fusion Graph Document
4747
---------------
48-
schema @fusion(version: 1) @httpClient(subgraph: "Accounts", baseAddress: "http:\/\/localhost:5000\/graphql") @webSocketClient(subgraph: "Accounts", baseAddress: "ws:\/\/localhost:5000\/graphql") {
48+
schema @fusion(version: 1) @httpClient(subgraph: "Accounts", clientName: "Fusion", baseAddress: "http:\/\/localhost:5000\/graphql") @webSocketClient(subgraph: "Accounts", clientName: "Fusion", baseAddress: "ws:\/\/localhost:5000\/graphql") {
4949
query: Query
5050
mutation: Mutation
5151
}
@@ -94,7 +94,7 @@ Accounts Subgraph Configuration
9494
"Name": "Accounts",
9595
"Schema": "schema {\n query: Query\n mutation: Mutation\n}\n\n\"The node interface is implemented by entities that have a global unique identifier.\"\ninterface Node {\n id: ID!\n}\n\ntype Query {\n \"Fetches an object given its ID.\"\n node(\"ID of the object.\" id: ID!): Node\n \"Lookup nodes by a list of IDs.\"\n nodes(\"The list of node IDs.\" ids: [ID!]!): [Node]!\n users: [User!]!\n userById(id: ID!): User\n usersById(ids: [ID!]!): [User!]!\n}\n\ntype Mutation {\n addUser(input: AddUserInput!): AddUserPayload!\n}\n\ntype User implements Node {\n id: ID!\n name: String!\n birthdate: Date!\n username: String!\n}\n\n\"The `DateTime` scalar represents an ISO-8601 compliant date time type.\"\nscalar DateTime\n\n\"The `Date` scalar represents an ISO-8601 compliant date type.\"\nscalar Date\n\ninput AddUserInput {\n name: String!\n username: String!\n birthdate: DateTime!\n}\n\ntype AddUserPayload {\n user: User\n}",
9696
"Extensions": [
97-
"extend type Query {\n userById(id: ID! @is(field: \"id\")): User!\n usersById(ids: [ID!]! @is(field: \"id\")): [User!]!\n}"
97+
"extend type Query {\n userById(id: ID!\n @is(field: \"id\")): User!\n usersById(ids: [ID!]!\n @is(field: \"id\")): [User!]!\n}"
9898
],
9999
"Clients": [
100100
{

0 commit comments

Comments
 (0)