Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed: ResolverResult value constructor ignores value parameter #217

Merged
merged 4 commits into from
Aug 24, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}
}
}