Skip to content

Commit

Permalink
Added more type extension overloads for schema stitching. (#2684)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelstaib committed Nov 27, 2020
1 parent 21ea620 commit c7f30fb
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 8 deletions.
Expand Up @@ -2,6 +2,7 @@
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection.Extensions;
Expand All @@ -20,6 +21,7 @@
using HotChocolate.Stitching.Utilities;
using HotChocolate.Utilities;
using HotChocolate.Utilities.Introspection;
using ThrowHelper = HotChocolate.Stitching.ThrowHelper;

namespace Microsoft.Extensions.DependencyInjection
{
Expand Down Expand Up @@ -571,6 +573,71 @@ await loadSchema(services, cancellationToken)
});
}

/// <summary>
/// Adds a schema SDL that contains type extensions.
/// Extension documents can be used to extend merged types
/// or even replace them.
/// </summary>
/// <param name="builder">
/// The <see cref="IRequestExecutorBuilder"/>.
/// </param>
/// <param name="assembly">
/// The assembly from which the type extension file shall be resolved.
/// </param>
/// <param name="key">
/// The resource key of the type extension file
/// </param>
/// <returns>
/// Returns the <see cref="IRequestExecutorBuilder"/>.
/// </returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="builder"/> is <c>null</c>, or
/// <paramref name="assembly"/> is <c>null</c>.
/// <paramref name="key"/> is <c>null</c>.
/// </exception>
public static IRequestExecutorBuilder AddTypeExtensionsFromResource(
this IRequestExecutorBuilder builder,
Assembly assembly,
string key)
{
if (builder is null)
{
throw new ArgumentNullException(nameof(builder));
}

if (assembly is null)
{
throw new ArgumentNullException(nameof(assembly));
}

if (key is null)
{
throw new ArgumentNullException(nameof(key));
}

return builder.ConfigureSchemaAsync(
async (s, ct) =>
{
Stream? stream = assembly.GetManifestResourceStream(key);
if (stream is null)
{
throw ThrowHelper.RequestExecutorBuilder_ResourceNotFound(key);
}
#if NET5_0
await using (stream)
#else
using (stream)
#endif
{
var buffer = new byte[stream.Length];
await stream.ReadAsync(buffer, 0, buffer.Length, ct).ConfigureAwait(false);
s.AddTypeExtensions(Utf8GraphQLParser.Parse(buffer));
}
});
}

/// <summary>
/// Add a document rewriter that is executed on
/// the merged schema document.
Expand Down
23 changes: 16 additions & 7 deletions src/HotChocolate/Stitching/src/Stitching/ThrowHelper.cs
Expand Up @@ -10,21 +10,21 @@ internal static class ThrowHelper
{
public static InvalidOperationException BufferedRequest_VariableDoesNotExist(
string name) =>
new InvalidOperationException(string.Format(
new(string.Format(
ThrowHelper_BufferedRequest_VariableDoesNotExist,
name));

public static InvalidOperationException BufferedRequest_OperationNotFound(
DocumentNode document) =>
new InvalidOperationException(string.Format(
new(string.Format(
ThrowHelper_BufferedRequest_OperationNotFound,
document));

public static GraphQLException ArgumentScopedVariableResolver_InvalidArgumentName(
string variableName,
FieldNode fieldSelection,
Path path) =>
new GraphQLException(ErrorBuilder.New()
new(ErrorBuilder.New()
.SetMessage(
StitchingResources.ArgumentScopedVariableResolver_InvalidArgumentName,
variableName)
Expand All @@ -37,7 +37,7 @@ internal static class ThrowHelper
string variableName,
FieldNode fieldSelection,
Path path) =>
new GraphQLException(ErrorBuilder.New()
new(ErrorBuilder.New()
.SetMessage(
StitchingResources.FieldScopedVariableResolver_InvalidFieldName,
variableName)
Expand All @@ -50,7 +50,7 @@ internal static class ThrowHelper
string scopeName,
FieldNode fieldSelection,
Path path) =>
new GraphQLException(ErrorBuilder.New()
new(ErrorBuilder.New()
.SetMessage(
StitchingResources.RootScopedVariableResolver_ScopeNotSupported,
scopeName)
Expand All @@ -61,7 +61,7 @@ internal static class ThrowHelper

public static SchemaException PublishSchemaDefinitionDescriptor_ResourceNotFound(
string key) =>
new SchemaException(
new(
SchemaErrorBuilder.New()
.SetMessage(
"The resource `{0}` was not found!",
Expand All @@ -70,10 +70,19 @@ internal static class ThrowHelper

public static SchemaException IntrospectionHelper_UnableToFetchSchemaDefinition(
IReadOnlyList<IError> errors) =>
new SchemaException(
new(
SchemaErrorBuilder.New()
.SetMessage("Unable to fetch schema definition.")
.SetExtension("errors", errors)
.Build());

public static SchemaException RequestExecutorBuilder_ResourceNotFound(
string key) =>
new(
SchemaErrorBuilder.New()
.SetMessage(
"The resource `{0}` was not found!",
key)
.Build());
}
}
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using HotChocolate.Configuration;
using HotChocolate.Execution;
Expand Down Expand Up @@ -34,6 +35,45 @@ public async Task RewriteType()
Assert.Equal("OriginalType1", lookup[("NewType1", "Schema1")]);
Assert.Equal("OriginalType2", lookup[("NewType2", "Schema2")]);
}

[Fact]
public void AddTypeExtensionsFromResource_Builder_Is_Null()
{
// arrange
// act
void Configure() =>
HotChocolateStitchingRequestExecutorExtensions
.AddTypeExtensionsFromResource(null!, GetType().Assembly, "abc");

// assert
Assert.Throws<ArgumentNullException>(Configure);
}

[Fact]
public void AddTypeExtensionsFromResource_Assembly_Is_Null()
{
// arrange
// act
void Configure() =>
new ServiceCollection().AddGraphQL()
.AddTypeExtensionsFromResource(null!, "abc");

// assert
Assert.Throws<ArgumentNullException>(Configure);
}

[Fact]
public void AddTypeExtensionsFromResource_Key_Is_Null()
{
// arrange
// act
void Configure() =>
new ServiceCollection().AddGraphQL()
.AddTypeExtensionsFromResource(GetType().Assembly, null!);

// assert
Assert.Throws<ArgumentNullException>(Configure);
}
}

public class CustomQueryType : ObjectType
Expand Down
Expand Up @@ -28,4 +28,11 @@
</None>
</ItemGroup>

<ItemGroup>
<None Remove="__resources__\DummyDirective.graphql" />
<EmbeddedResource Include="__resources__\DummyDirective.graphql">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>

</Project>
Expand Up @@ -693,5 +693,56 @@ public async Task Add_Dummy_Directive()
// assert
Assert.NotNull(schema.GetDirectiveType("foo"));
}

[Fact]
public async Task Add_Dummy_Directive_From_Resource()
{
// arrange
IHttpClientFactory httpClientFactory =
Context.CreateDefaultRemoteSchemas();

// act
ISchema schema =
await new ServiceCollection()
.AddSingleton(httpClientFactory)
.AddGraphQL()
.AddRemoteSchema(Context.ContractSchema)
.AddRemoteSchema(Context.CustomerSchema)
.AddTypeExtensionsFromResource(
GetType().Assembly,
"HotChocolate.Stitching.__resources__.DummyDirective.graphql")
.ModifyRequestOptions(o => o.IncludeExceptionDetails = true)
.BuildSchemaAsync();

// assert
Assert.NotNull(schema.GetDirectiveType("foo"));
}

[Fact]
public async Task Add_Dummy_Directive_From_Resource_Key_Does_Not_Exist()
{
// arrange
IHttpClientFactory httpClientFactory =
Context.CreateDefaultRemoteSchemas();

// act
async Task Configure() =>
await new ServiceCollection()
.AddSingleton(httpClientFactory)
.AddGraphQL()
.AddRemoteSchema(Context.ContractSchema)
.AddRemoteSchema(Context.CustomerSchema)
.AddTypeExtensionsFromResource(
GetType().Assembly,
"HotChocolate.Stitching.__resources__.abc")
.ModifyRequestOptions(o => o.IncludeExceptionDetails = true)
.BuildSchemaAsync();

// assert
SchemaException exception = await Assert.ThrowsAsync<SchemaException>(Configure);
Assert.Contains(
"The resource `HotChocolate.Stitching.__resources__.abc` was not found!",
exception.Message);
}
}
}
@@ -0,0 +1 @@
directive @foo on FIELD_DEFINITION

0 comments on commit c7f30fb

Please sign in to comment.