Skip to content

Commit

Permalink
Added better error when root value cannot be created. (#2598)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelstaib committed Nov 17, 2020
1 parent 5daa147 commit 524d73c
Show file tree
Hide file tree
Showing 18 changed files with 112 additions and 91 deletions.
6 changes: 2 additions & 4 deletions ErrorCodes.md
@@ -1,5 +1,5 @@
| Code | Category | Description |
|--------|-------------------|------------------------------------------------------------------------------------------------------------|
| ------ | ----------------- | ---------------------------------------------------------------------------------------------------------- |
| HC0001 | Scalars | The runtime type is not supported by the scalars ParseValue method. |
| HC0002 | Scalars | Either the syntax node is invalid when parsing the literal or the syntax node value has an invalid format. |
| HC0003 | Apollo Federation | The key attribute is used on the type level without specifying the fieldset. |
Expand All @@ -18,6 +18,4 @@
| HC0016 | Execution | Variable `xyz` got an invalid value. |
| HC0017 | Execution | Variable `xyz` is not an input type. |
| HC0018 | Execution | Variable `xyz` is required. |



| HC0019 | Execution | Unable to create an instance for the operation type (initial value). |
1 change: 1 addition & 0 deletions src/HotChocolate/Core/src/Abstractions/ErrorCodes.cs
Expand Up @@ -25,6 +25,7 @@ public static class Execution
public const string QueryNotFound = "HC0015";
public const string TaskProcessingError = "HC0008";
public const string SyntaxError = "HC0014";
public const string CannotCreateRootValue = "HC0019";
}

public static class Server
Expand Down
Expand Up @@ -72,7 +72,7 @@ internal sealed class OperationExecutionMiddleware

private async Task ExecuteOperationAsync(
IRequestContext context,
IBatchDispatcher? batchDispatcher,
IBatchDispatcher batchDispatcher,
IPreparedOperation operation)
{
if (operation.Definition.Operation == OperationType.Subscription)
Expand Down Expand Up @@ -129,15 +129,15 @@ internal sealed class OperationExecutionMiddleware

private async Task<IQueryResult?> ExecuteQueryOrMutationAsync(
IRequestContext context,
IBatchDispatcher? batchDispatcher,
IBatchDispatcher batchDispatcher,
IPreparedOperation operation,
OperationContext operationContext)
{
IQueryResult? result = null;

if (operation.Definition.Operation == OperationType.Query)
{
object? query = RootValueResolver.TryResolve(
object? query = RootValueResolver.Resolve(
context,
context.Services,
operation.RootType,
Expand All @@ -149,15 +149,15 @@ internal sealed class OperationExecutionMiddleware
batchDispatcher,
operation,
query,
context.Variables);
context.Variables!);

result = await _queryExecutor
.ExecuteAsync(operationContext)
.ConfigureAwait(false);
}
else if (operation.Definition.Operation == OperationType.Mutation)
{
object? mutation = RootValueResolver.TryResolve(
object? mutation = RootValueResolver.Resolve(
context,
context.Services,
operation.RootType,
Expand All @@ -169,7 +169,7 @@ internal sealed class OperationExecutionMiddleware
batchDispatcher,
operation,
mutation,
context.Variables);
context.Variables!);

result = await _mutationExecutor
.ExecuteAsync(operationContext)
Expand Down
@@ -1,36 +1,66 @@
using System;
using HotChocolate.Execution.Properties;
using HotChocolate.Types;
using static HotChocolate.Execution.Properties.Resources;

namespace HotChocolate.Execution.Processing
{
/// <summary>
/// This helper will resolve the initial value for the execution engine.
/// </summary>
internal static class RootValueResolver
{
public static object? TryResolve(
public static object? Resolve(
IRequestContext context,
IServiceProvider services,
ObjectType rootType,
ref object? cachedValue)
{
if (context.Request.InitialValue is { })
// if an initial value was passed in with the request by the user, we will use that
// as root value on which the execution engine starts executing.
if (context.Request.InitialValue is not null)
{
return context.Request.InitialValue;
}

if (cachedValue is { })
// if the initial value is a singleton and was already resolved,
// we will use this instance.
if (cachedValue is not null)
{
return cachedValue;
}

// if the operation type has a proper runtime representation we will try to resolve
// that from the request services.
if (rootType.RuntimeType != typeof(object))
{
object? rootValue = services.GetService(rootType.RuntimeType);

// if the request services did not provide a rootValue and the runtime
// representation is a instantiatable class we will create a singleton ourselfs
// and store it as cached value in order to reuse it.
if (rootValue is null &&
!rootType.RuntimeType.IsAbstract &&
!rootType.RuntimeType.IsInterface)
{
rootValue = context.Activator.CreateInstance(rootType.RuntimeType);
cachedValue = rootValue;
try
{
rootValue = context.Activator.CreateInstance(rootType.RuntimeType);
cachedValue = rootValue;
}
catch
{
throw new GraphQLException(
ErrorBuilder.New()
.SetMessage(
RootValueResolver_Resolve_CannotCreateInstance,
rootType.Name.Value,
rootType.RuntimeType.FullName ?? rootType.RuntimeType.Name)
.SetCode(ErrorCodes.Execution.CannotCreateRootValue)
.SetExtension("operationType", rootType.Name)
.SetExtension("runtimeType", rootType.RuntimeType.FullName)
.Build());
}
}

return rootValue;
Expand Down
Expand Up @@ -103,7 +103,7 @@ private async Task<IQueryResult> OnEvent(object payload)
ImmutableDictionary<string, object?>.Empty
.SetItem(WellKnownContextData.EventMessage, payload);

object? rootValue = RootValueResolver.TryResolve(
object? rootValue = RootValueResolver.Resolve(
_requestContext,
eventServices,
_subscriptionType,
Expand Down Expand Up @@ -137,7 +137,7 @@ private async ValueTask<ISourceStream> SubscribeAsync()
{
// first we will create the root value which essentially
// is the subscription object. In some cases this object is null.
object? rootValue = RootValueResolver.TryResolve(
object? rootValue = RootValueResolver.Resolve(
_requestContext,
_requestContext.Services,
_subscriptionType,
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions src/HotChocolate/Core/src/Execution/Properties/Resources.resx
Expand Up @@ -243,4 +243,10 @@
<data name="RequestExecutorBuilder_Convention_NotSuppported" xml:space="preserve">
<value>The specified convention type is not supported.</value>
</data>
<data name="RequestExecutorBuilder_Convention_NotSuppported" xml:space="preserve">
<value>The specified convention type is not supported.</value>
</data>
<data name="RootValueResolver_Resolve_CannotCreateInstance" xml:space="preserve">
<value>Unable to create the operation type `{0}` instance. Try adding the following service: `services.AddScoped&lt;{1}&gt;();`</value>
</data>
</root>
26 changes: 24 additions & 2 deletions src/HotChocolate/Core/test/Execution.Tests/CodeFirstTests.cs
Expand Up @@ -4,7 +4,9 @@
using System.Threading;
using System.Threading.Tasks;
using HotChocolate.Resolvers;
using HotChocolate.Tests;
using HotChocolate.Types;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using Snapshooter.Xunit;
using Xunit;
Expand Down Expand Up @@ -263,6 +265,17 @@ public async Task ExecuteExplicitAsyncField()
result.MatchSnapshot();
}

[Fact]
public async Task CannotCreateRootValue()
{
IExecutionResult result =
await new ServiceCollection()
.AddGraphQL()
.AddQueryType<QueryPrivateConstructor>()
.ExecuteRequestAsync("{ hello }")
.MatchSnapshotAsync();
}

private static Schema CreateSchema()
{
return Schema.Create(c =>
Expand All @@ -286,7 +299,7 @@ public string GetTest()

public IExecutable<string> GetQuery()
{
return new MockExecutable<string>(new []{ "foo", "bar" }.AsQueryable());
return new MockExecutable<string>(new[] { "foo", "bar" }.AsQueryable());
}

public string TestProp => "Hello World!";
Expand Down Expand Up @@ -471,7 +484,7 @@ public MockExecutable(IQueryable<T> source)
_source = source;
}

public object Source =>_source;
public object Source => _source;

public ValueTask<IList> ToListAsync(CancellationToken cancellationToken)
{
Expand All @@ -493,5 +506,14 @@ public string Print()
return _source.ToString();
}
}

public class QueryPrivateConstructor
{
private QueryPrivateConstructor()
{
}

public string Hello() => "Hello";
}
}
}
@@ -0,0 +1,12 @@
{
"errors": [
{
"message": "Unable to create the operation type \u0060QueryPrivateConstructor\u0060 instance. Try adding the following service: \u0060services.AddScoped\u003CHotChocolate.Execution.CodeFirstTests\u002BQueryPrivateConstructor\u003E();\u0060",
"extensions": {
"code": "HC0019",
"operationType": "QueryPrivateConstructor",
"runtimeType": "HotChocolate.Execution.CodeFirstTests\u002BQueryPrivateConstructor"
}
}
]
}
@@ -1,16 +1,10 @@
{
"Label": null,
"Path": null,
"Data": {
"data": {
"dog": {
"names": [
"a",
"b"
]
}
},
"Errors": null,
"Extensions": null,
"ContextData": null,
"HasNext": null
}
}
@@ -1,13 +1,7 @@
{
"Label": null,
"Path": null,
"Data": {
"data": {
"dog": {
"name2": "b"
}
},
"Errors": null,
"Extensions": null,
"ContextData": null,
"HasNext": null
}
}
@@ -1,13 +1,7 @@
{
"Label": null,
"Path": null,
"Data": {
"data": {
"dog": {
"name": "a"
}
},
"Errors": null,
"Extensions": null,
"ContextData": null,
"HasNext": null
}
}
@@ -1,13 +1,7 @@
{
"Label": null,
"Path": null,
"Data": {
"data": {
"dog": {
"desc": "desc"
}
},
"Errors": null,
"Extensions": null,
"ContextData": null,
"HasNext": null
}
}
@@ -1,11 +1,5 @@
{
"Label": null,
"Path": null,
"Data": {
"data": {
"test": "Hello World!"
},
"Errors": null,
"Extensions": null,
"ContextData": null,
"HasNext": null
}
}
@@ -1,11 +1,5 @@
{
"Label": null,
"Path": null,
"Data": {
"data": {
"test": "Hello World!"
},
"Errors": null,
"Extensions": null,
"ContextData": null,
"HasNext": null
}
}
@@ -1,14 +1,8 @@
{
"Label": null,
"Path": null,
"Data": {
"data": {
"query": [
"foo",
"bar"
]
},
"Errors": null,
"Extensions": null,
"ContextData": null,
"HasNext": null
}
}
@@ -1,13 +1,7 @@
{
"Label": null,
"Path": null,
"Data": {
"data": {
"drink": {
"kind": "BLACK_TEA"
}
},
"Errors": null,
"Extensions": null,
"ContextData": null,
"HasNext": null
}
}

0 comments on commit 524d73c

Please sign in to comment.