Skip to content

Commit

Permalink
Adds Remove to Fusion Compose
Browse files Browse the repository at this point in the history
  • Loading branch information
PascalSenn committed Oct 9, 2023
1 parent 23e4e1e commit 688156b
Show file tree
Hide file tree
Showing 5 changed files with 255 additions and 7 deletions.
49 changes: 44 additions & 5 deletions src/HotChocolate/Fusion/src/Abstractions/FusionGraphPackage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ public async Task<IReadOnlyList<SubgraphConfiguration>> GetSubgraphConfiguration
/// <exception cref="FusionGraphPackageException">
/// The Fusion graph package must be opened in read/write mode to update contents.
/// </exception>
public Task SetSubgraphConfigurationAsync(
public async Task SetSubgraphConfigurationAsync(
SubgraphConfiguration configuration,
CancellationToken cancellationToken = default)
{
Expand All @@ -430,21 +430,60 @@ public Task SetSubgraphConfigurationAsync(
throw new FusionGraphPackageException(FusionGraphPackage_CannotWrite);
}

if (_package.RelationshipExists(configuration.Name))
await RemoveSubgraphConfigurationAsync(configuration.Name, cancellationToken);

await WriteSubgraphConfigurationAsync(configuration, cancellationToken);
}

/// <summary>
/// Removes a subgraph configuration from the package.
/// </summary>
/// <param name="subgraphName">
/// The name of the subgraph configuration to remove.
/// </param>
/// <param name="cancellationToken">
/// The cancellation token.
/// </param>
/// <returns>
/// A <see cref="Task"/> representing the asynchronous operation.
/// </returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="subgraphName"/> is <c>null</c>.
/// </exception>
/// <exception cref="FusionGraphPackageException">
/// The Fusion graph package must be opened in read/write mode to update contents.
/// </exception>
public Task RemoveSubgraphConfigurationAsync(
string subgraphName,
CancellationToken cancellationToken = default)
{
if (subgraphName is null)
{
var rootRel = _package.GetRelationship(configuration.Name);
throw new ArgumentNullException(nameof(subgraphName));
}

if (_package.FileOpenAccess != FileAccess.ReadWrite)
{
throw new FusionGraphPackageException(FusionGraphPackage_CannotWrite);
}

if (_package.RelationshipExists(subgraphName))
{
var rootRel = _package.GetRelationship(subgraphName);
var rootPart = _package.GetPart(rootRel.TargetUri);

foreach (var relationship in rootPart.GetRelationships())
{
_package.DeletePart(relationship.TargetUri);
}

_package.DeleteRelationship(configuration.Name);
_package.DeleteRelationship(subgraphName);
_package.DeletePart(rootPart.Uri);
}

return WriteSubgraphConfigurationAsync(configuration, cancellationToken);
_package.Flush();

return Task.CompletedTask;
}

private static async Task<DocumentNode> ReadSchemaPartAsync(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ public static async Task<SubgraphConfigJson> ParseAsync(
case "extensions":
extensions = property.Value.SafeClone();
break;

default:
throw new NotSupportedException(
$"Configuration property `{property.Value}` is not supported.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ public ComposeCommand() : base("compose")
fusionPackageSettingsFile.AddAlias("--package-settings");
fusionPackageSettingsFile.AddAlias("--settings");

var removeSubgraphs = new Option<List<string>?>("--remove");
removeSubgraphs.AddAlias("-r");

var workingDirectory = new WorkingDirectoryOption();

var enableNodes = new Option<bool?>("--enable-nodes");
Expand All @@ -43,12 +46,14 @@ public ComposeCommand() : base("compose")
AddOption(subgraphPackageFile);
AddOption(workingDirectory);
AddOption(enableNodes);
AddOption(removeSubgraphs);

this.SetHandler(
ExecuteAsync,
Bind.FromServiceProvider<IConsole>(),
fusionPackageFile,
subgraphPackageFile,
removeSubgraphs,
fusionPackageSettingsFile,
workingDirectory,
enableNodes,
Expand All @@ -61,6 +66,7 @@ private static async Task ExecuteAsync(
IConsole console,
FileInfo packageFile,
List<string>? subgraphPackageFiles,
List<string>? removeSubgraphs,
FileInfo? settingsFile,
DirectoryInfo workingDirectory,
bool? enableNodes,
Expand Down Expand Up @@ -94,8 +100,21 @@ private static async Task ExecuteAsync(

await using var package = FusionGraphPackage.Open(packageFile.FullName);

if(removeSubgraphs is not null)
{
foreach (var subgraph in removeSubgraphs)
{
await package.RemoveSubgraphConfigurationAsync(subgraph, cancellationToken);
}
}

var configs = (await package.GetSubgraphConfigurationsAsync(cancellationToken)).ToDictionary(t => t.Name);
await ResolveSubgraphPackagesAsync(workingDirectory, subgraphPackageFiles, configs, cancellationToken);

// resolve subraph packages will scan the directory for fsp's. In case of remove we don't want to do that.
if (removeSubgraphs is not { Count: > 0 } || subgraphPackageFiles is { Count: > 0 })
{
await ResolveSubgraphPackagesAsync(workingDirectory, subgraphPackageFiles, configs, cancellationToken);
}

using var settingsJson = settingsFile.Exists
? JsonDocument.Parse(await File.ReadAllTextAsync(settingsFile.FullName, cancellationToken))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,4 +265,67 @@ public async Task Compose_Loose_Subgraph_Files()

snapshot.MatchSnapshot();
}

[Fact]
public async Task Compose_Fusion_Graph_Remove_Subgraph()
{
// arrange
using var demoProject = await DemoProject.CreateAsync();
var accountConfig = demoProject.Accounts.ToConfiguration(AccountsExtensionSdl);
var account = CreateFiles(accountConfig);
var accountSubgraphPackageFile = CreateTempFile();

await PackageHelper.CreateSubgraphPackageAsync(
accountSubgraphPackageFile,
new SubgraphFiles(
account.SchemaFile,
account.TransportConfigFile,
account.ExtensionFiles));

var reviewConfig = demoProject.Reviews2.ToConfiguration(Reviews2ExtensionSdl);
var review = CreateFiles(reviewConfig);
var reviewSubgraphPackageFile = CreateTempFile();

await PackageHelper.CreateSubgraphPackageAsync(
reviewSubgraphPackageFile,
new SubgraphFiles(
review.SchemaFile,
review.TransportConfigFile,
review.ExtensionFiles));

var packageFile = CreateTempFile(Extensions.FusionPackage);

var app = App.CreateBuilder().Build();
await app.InvokeAsync(new[] { "compose", "-p", packageFile, "-s", accountSubgraphPackageFile });

app = App.CreateBuilder().Build();
await app.InvokeAsync(
new[] { "compose", "-p", packageFile, "-s", reviewSubgraphPackageFile });

// act
app = App.CreateBuilder().Build();
await app.InvokeAsync(
new[] { "compose", "-p", packageFile, "-r", "Reviews2" });

// assert
Assert.True(File.Exists(packageFile));

await using var package = FusionGraphPackage.Open(packageFile, FileAccess.Read);

var fusionGraph = await package.GetFusionGraphAsync();
var schema = await package.GetSchemaAsync();
var subgraphs = await package.GetSubgraphConfigurationsAsync();

var snapshot = new Snapshot();

snapshot.Add(schema, "Schema Document");
snapshot.Add(fusionGraph, "Fusion Graph Document");

foreach (var subgraph in subgraphs)
{
snapshot.Add(subgraph, $"{subgraph.Name} Subgraph Configuration");
}

snapshot.MatchSnapshot();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
Schema Document
---------------
schema {
query: Query
mutation: Mutation
}

type Query {
userById(id: ID!): User
users: [User!]!
usersById(ids: [ID!]!): [User!]!
viewer: Viewer!
}

type Mutation {
addUser(input: AddUserInput!): AddUserPayload!
}

type AddUserPayload {
user: User
}

type SomeData {
accountValue: String!
}

type User implements Node {
birthdate: Date!
id: ID!
name: String!
username: String!
}

type Viewer {
data: SomeData!
user: User
}

"The node interface is implemented by entities that have a global unique identifier."
interface Node {
id: ID!
}

input AddUserInput {
birthdate: Date!
name: String!
username: String!
}

"The `Date` scalar represents an ISO-8601 compliant date type."
scalar Date
---------------

Fusion Graph Document
---------------
schema @fusion(version: 1) @transport(subgraph: "Accounts", group: "Fusion", location: "http:\/\/localhost:5000\/graphql", kind: "HTTP") @transport(subgraph: "Accounts", group: "Fusion", location: "ws:\/\/localhost:5000\/graphql", kind: "WebSocket") {
query: Query
mutation: Mutation
}

type Query {
userById(id: ID!): User @variable(subgraph: "Accounts", name: "id", argument: "id") @resolver(subgraph: "Accounts", select: "{ userById(id: $id) }", arguments: [ { name: "id", type: "ID!" } ])
users: [User!]! @resolver(subgraph: "Accounts", select: "{ users }")
usersById(ids: [ID!]!): [User!]! @variable(subgraph: "Accounts", name: "ids", argument: "ids") @resolver(subgraph: "Accounts", select: "{ usersById(ids: $ids) }", arguments: [ { name: "ids", type: "[ID!]!" } ])
viewer: Viewer! @resolver(subgraph: "Accounts", select: "{ viewer }")
}

type Mutation {
addUser(input: AddUserInput!): AddUserPayload! @variable(subgraph: "Accounts", name: "input", argument: "input") @resolver(subgraph: "Accounts", select: "{ addUser(input: $input) }", arguments: [ { name: "input", type: "AddUserInput!" } ])
}

type AddUserPayload {
user: User @source(subgraph: "Accounts")
}

type SomeData {
accountValue: String! @source(subgraph: "Accounts")
}

type User implements Node @variable(subgraph: "Accounts", name: "User_id", select: "id") @resolver(subgraph: "Accounts", select: "{ userById(id: $User_id) }", arguments: [ { name: "User_id", type: "ID!" } ]) @resolver(subgraph: "Accounts", select: "{ usersById(ids: $User_id) }", arguments: [ { name: "User_id", type: "[ID!]!" } ], kind: "BATCH") {
birthdate: Date! @source(subgraph: "Accounts")
id: ID! @source(subgraph: "Accounts")
name: String! @source(subgraph: "Accounts")
username: String! @source(subgraph: "Accounts")
}

type Viewer {
data: SomeData! @source(subgraph: "Accounts")
user: User @source(subgraph: "Accounts")
}

"The node interface is implemented by entities that have a global unique identifier."
interface Node {
id: ID!
}

input AddUserInput {
birthdate: Date!
name: String!
username: String!
}

"The `Date` scalar represents an ISO-8601 compliant date type."
scalar Date
---------------

Accounts Subgraph Configuration
---------------
{
"Name": "Accounts",
"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 viewer: Viewer!\n}\n\ntype Mutation {\n addUser(input: AddUserInput!): AddUserPayload!\n}\n\n\"The `Date` scalar represents an ISO-8601 compliant date type.\"\nscalar Date\n\ntype User implements Node {\n id: ID!\n name: String!\n birthdate: Date!\n username: String!\n}\n\ntype Viewer {\n user: User\n data: SomeData!\n}\n\ntype SomeData {\n accountValue: String!\n}\n\ninput AddUserInput {\n name: String!\n username: String!\n birthdate: Date!\n}\n\ntype AddUserPayload {\n user: User\n}",
"Extensions": [
"extend type Query {\n userById(id: ID!\n @is(field: \"id\")): User!\n usersById(ids: [ID!]!\n @is(field: \"id\")): [User!]!\n}"
],
"Clients": [
{
"ClientName": null,
"BaseAddress": "http://localhost:5000/graphql"
},
{
"ClientName": null,
"BaseAddress": "ws://localhost:5000/graphql"
}
],
"ConfigurationExtensions": null
}
---------------

0 comments on commit 688156b

Please sign in to comment.