Skip to content

Commit

Permalink
Fixed: ResolverResult value constructor ignores value parameter (#217)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelstaib authored Aug 24, 2018
1 parent 62afad5 commit 8206934
Show file tree
Hide file tree
Showing 7 changed files with 236 additions and 18 deletions.
26 changes: 26 additions & 0 deletions src/Abstractions/IResolverResult.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,43 @@
namespace HotChocolate
{
/// <summary>
/// A resolver result represents an error or a value that is returned by the
/// field resolver. This interface provides a way to path field errors to
/// the execution engine without throwing QueryExceptions.
/// </summary>
public interface IResolverResult
{
/// <summary>
/// The error message that shall be used to create a
/// field error if the resolver result represents an error.
/// </summary>
string ErrorMessage { get; }

/// <summary>
/// Defines if the resolver result instance represents
/// an error <c>true</c> or a value <c>false</c>.
/// </summary>
bool IsError { get; }

/// <summary>
/// The resolver result value that shall be processed by the
/// execution engine in case this resolver is not an error.
/// </summary>
object Value { get; }
}

/// <summary>
/// A resolver result represents an error or a value that is returned by the
/// field resolver. This interface provides a way to path field errors to
/// the execution engine without throwing QueryExceptions.
/// </summary>
public interface IResolverResult<out TValue>
: IResolverResult
{
/// <summary>
/// The resolver result value that shall be processed by the
/// execution engine in case this resolver is not an error.
/// </summary>
new TValue Value { get; }
}
}
55 changes: 50 additions & 5 deletions src/Abstractions/ResolverResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,75 @@

namespace HotChocolate
{
/// <summary>
/// A resolver result represents an error or a value that is returned by the
/// field-resolver. This interface provides a way to path field errors to
/// the execution engine without throwing QueryExceptions.
/// </summary>
public readonly struct ResolverResult<TValue>
: IResolverResult<TValue>
{
public ResolverResult(string errorMessage)
private ResolverResult(string errorMessage)
{
Value = default;
ErrorMessage = errorMessage
?? throw new ArgumentNullException(nameof(errorMessage));
IsError = true;
}

public ResolverResult(TValue value)
private ResolverResult(TValue value)
{
Value = default;
Value = value;
ErrorMessage = null;
IsError = false;
}

public TValue Value { get; }

/// <summary>
/// The error message that shall be used to create a
/// field error if the resolver result represents an error.
/// </summary>
public string ErrorMessage { get; }

/// <summary>
/// Defines if the resolver result instance represents
/// an error <c>true</c> or a value <c>false</c>.
/// </summary>
public bool IsError { get; }

/// <summary>
/// The resolver result value that shall be processed by the
/// execution engine in case this resolver is not an error.
/// </summary>
public TValue Value { get; }

object IResolverResult.Value => Value;

/// <summary>
/// Creates a field error resolver result.
/// </summary>
/// <param name="errorMessage">
/// The error message.
/// </param>
/// <returns>
/// Returns a field error resolver result.
/// </returns>
public static ResolverResult<TValue> CreateError(string errorMessage)
{
return new ResolverResult<TValue>(errorMessage);
}

/// <summary>
/// Creates a value resolver result.
/// </summary>
/// <param name="value">
/// The reolver result value.
/// </param>
/// <returns>
/// Returns a value resolver result.
/// </returns>
public static ResolverResult<TValue> CreateValue(TValue value)
{
return new ResolverResult<TValue>(value);
}
}
}
60 changes: 59 additions & 1 deletion src/Core.Tests/Execution/QueryExecuterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -220,19 +220,77 @@ public async Task ExecuteQueryWith2OperationsAndInvalidOperationName_Error()
Assert.Equal(Snapshot.Current(), Snapshot.New(result));
}

[Fact]
public async Task ExecuteFieldWithResolverResult()
{
// arrange
Dictionary<string, IValueNode> variableValues =
new Dictionary<string, IValueNode>();

Schema schema = CreateSchema();
QueryExecuter executer = new QueryExecuter(schema);
QueryRequest request = new QueryRequest("{ x xasync }");

// act
IExecutionResult result = await executer.ExecuteAsync(request);

// assert
Assert.Null(result.Errors);
Assert.Equal(Snapshot.Current(), Snapshot.New(result));
}

[Fact]
public async Task ExecuteFieldWithResolverResultError()
{
// arrange
Dictionary<string, IValueNode> variableValues =
new Dictionary<string, IValueNode>();

Schema schema = CreateSchema();
QueryExecuter executer = new QueryExecuter(schema);
QueryRequest request = new QueryRequest("{ y yasync }");

// act
IExecutionResult result = await executer.ExecuteAsync(request);

// assert
Assert.NotNull(result.Errors);
Assert.Equal(Snapshot.Current(), Snapshot.New(result));
}


private Schema CreateSchema()
{
return Schema.Create(@"
type Query {
a: String
b(a: String!): String
x: String
y: String
xasync: String
yasync: String
}
", c =>
{
c.BindResolver(() => "hello world a")
.To("Query", "a");
c.BindResolver(ctx => "hello world " + ctx.Argument<string>("a"))
c.BindResolver(
ctx => "hello world " + ctx.Argument<string>("a"))
.To("Query", "b");
c.BindResolver(
() => ResolverResult<string>.CreateValue("hello world x"))
.To("Query", "x");
c.BindResolver(
() => ResolverResult<string>.CreateError("hello world y"))
.To("Query", "y");
c.BindResolver(
async () => await Task.FromResult(
ResolverResult<string>.CreateValue("hello world xasync")))
.To("Query", "xasync");
c.BindResolver(
async () => await Task.FromResult(
ResolverResult<string>.CreateError("hello world yasync")))
.To("Query", "yasync");
});
}
}
Expand Down
38 changes: 38 additions & 0 deletions src/Core.Tests/ResolverResultTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System;
using Xunit;

namespace HotChocolate
{
public class ResolverResultTests
{
[Fact]
public void CreateValue()
{
// arrange
string value = Guid.NewGuid().ToString();

// act
var result = ResolverResult<string>.CreateValue(value);

// assert
Assert.Equal(value, result.Value);
Assert.False(result.IsError);
Assert.Null(result.ErrorMessage);
}

[Fact]
public void CreateError()
{
// arrange
string errorMessage = Guid.NewGuid().ToString();

// act
var result = ResolverResult<string>.CreateError(errorMessage);

// assert
Assert.Equal(errorMessage, result.ErrorMessage);
Assert.True(result.IsError);
Assert.Null(result.Value);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"Data": {
"x": "hello world x",
"xasync": "hello world xasync"
},
"Errors": null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"Data": {
"y": null,
"yasync": null
},
"Errors": [
{
"FieldName": "y",
"Locations": [
{
"Line": 1,
"Column": 3
}
],
"Message": "hello world y"
},
{
"FieldName": "yasync",
"Locations": [
{
"Line": 1,
"Column": 5
}
],
"Message": "hello world yasync"
}
]
}
40 changes: 28 additions & 12 deletions src/Core/Execution/ExecutionStrategyBase.Helpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ protected static object ExecuteResolver(
}

protected static async Task<object> FinalizeResolverResultAsync(
FieldNode fieldSelection, object resolverResult,
FieldNode fieldSelection,
object resolverResult,
bool isDeveloperMode)
{
switch (resolverResult)
Expand All @@ -44,26 +45,26 @@ protected static async Task<object> FinalizeResolverResultAsync(
fieldSelection, task, isDeveloperMode);

case IResolverResult result:
if (result.IsError)
{
return new FieldError(
result.ErrorMessage,
fieldSelection);
}
return result.Value;
return CompleteResolverResult(fieldSelection, result);

default:
return resolverResult;
}
}

private static async Task<object> FinalizeResolverResultTaskAsync(
FieldNode fieldSelection, Task<object> task,
FieldNode fieldSelection,
Task<object> task,
bool isDeveloperMode)
{
try
{
return await task;
object resolverResult = await task;
if (resolverResult is IResolverResult r)
{
return CompleteResolverResult(fieldSelection, r);
}
return resolverResult;
}
catch (QueryException ex)
{
Expand All @@ -77,7 +78,8 @@ private static async Task<object> FinalizeResolverResultTaskAsync(
}

private static IQueryError CreateErrorFromException(
Exception exception, FieldNode fieldSelection,
Exception exception,
FieldNode fieldSelection,
bool isDeveloperMode)
{
if (isDeveloperMode)
Expand All @@ -94,9 +96,23 @@ private static IQueryError CreateErrorFromException(
}
}

protected void CompleteValue(FieldValueCompletionContext completionContext)
protected void CompleteValue(
FieldValueCompletionContext completionContext)
{
_fieldValueCompleter.CompleteValue(completionContext);
}

private static object CompleteResolverResult(
FieldNode fieldSelection,
IResolverResult resolverResult)
{
if (resolverResult.IsError)
{
return new FieldError(
resolverResult.ErrorMessage,
fieldSelection);
}
return resolverResult.Value;
}
}
}

0 comments on commit 8206934

Please sign in to comment.