Skip to content

Commit

Permalink
Fixed StateAttribute support in resolver compilers. (#887)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelstaib authored Jul 2, 2019
1 parent c7e0cc8 commit 3b95fd0
Show file tree
Hide file tree
Showing 5 changed files with 289 additions and 8 deletions.
4 changes: 4 additions & 0 deletions src/Core/Abstractions/StateAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,9 @@ public StateAttribute(string key)
}

public string Key { get; }

public bool IsScoped { get; set; }

public bool DefaultIfNotExists { get; set; }
}
}
170 changes: 164 additions & 6 deletions src/Core/Types.Tests/Resolvers/ResolverPropertyGeneratorTests.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
using System;
using System.Collections.Immutable;
using System.Collections.Generic;
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using HotChocolate.Language;
using HotChocolate.Resolvers.CodeGeneration;
using HotChocolate.Resolvers.Expressions.Parameters;
using HotChocolate.Subscriptions;
using HotChocolate.Types;
using Moq;
using Snapshooter.Xunit;
using Xunit;

namespace HotChocolate.Resolvers.Expressions
Expand Down Expand Up @@ -738,6 +734,155 @@ public async Task Compile_Arguments_Service()
Assert.True(result);
}

[Fact]
public async Task Compile_Arguments_ContextData()
{
// arrange
Type type = typeof(Resolvers);
MemberInfo resolverMember =
type.GetMethod("ResolveWithContextData");
var resolverDescriptor = new ResolverDescriptor(
type,
new FieldMember("A", "b", resolverMember));
var contextData = new Dictionary<string, object>
{
{ "foo", "bar"}
};

// act
var compiler = new ResolverCompiler();
FieldResolver resolver = compiler.Compile(resolverDescriptor);

// assert
var context = new Mock<IResolverContext>();
context.Setup(t => t.Parent<Resolvers>()).Returns(new Resolvers());
context.Setup(t => t.ContextData).Returns(contextData);
string result = (string)await resolver.Resolver(context.Object);
Assert.Equal("bar", result);
}

[Fact]
public async Task Compile_Arguments_ContextData_DefaultValue()
{
// arrange
Type type = typeof(Resolvers);
MemberInfo resolverMember =
type.GetMethod("ResolveWithContextDataDefault");
var resolverDescriptor = new ResolverDescriptor(
type,
new FieldMember("A", "b", resolverMember));
var contextData = new Dictionary<string, object>();

// act
var compiler = new ResolverCompiler();
FieldResolver resolver = compiler.Compile(resolverDescriptor);

// assert
var context = new Mock<IResolverContext>();
context.Setup(t => t.Parent<Resolvers>()).Returns(new Resolvers());
context.Setup(t => t.ContextData).Returns(contextData);
object result = await resolver.Resolver(context.Object);
Assert.Null(result);
}

[Fact]
public void Compile_Arguments_ContextData_NotExists()
{
// arrange
Type type = typeof(Resolvers);
MemberInfo resolverMember =
type.GetMethod("ResolveWithContextData");
var resolverDescriptor = new ResolverDescriptor(
type,
new FieldMember("A", "b", resolverMember));
var contextData = new Dictionary<string, object>();

// act
var compiler = new ResolverCompiler();
FieldResolver resolver = compiler.Compile(resolverDescriptor);

// assert
var context = new Mock<IResolverContext>();
context.Setup(t => t.Parent<Resolvers>()).Returns(new Resolvers());
context.Setup(t => t.ContextData).Returns(contextData);
Action action = () => resolver.Resolver(context.Object);
Assert.Throws<ArgumentException>(action);
}

[Fact]
public async Task Compile_Arguments_ScopedContextData()
{
// arrange
Type type = typeof(Resolvers);
MemberInfo resolverMember =
type.GetMethod("ResolveWithScopedContextData");
var resolverDescriptor = new ResolverDescriptor(
type,
new FieldMember("A", "b", resolverMember));
var contextData = ImmutableDictionary<string, object>.Empty
.SetItem("foo", "bar");

// act
var compiler = new ResolverCompiler();
FieldResolver resolver = compiler.Compile(resolverDescriptor);

// assert
var context = new Mock<IResolverContext>();
context.Setup(t => t.Parent<Resolvers>()).Returns(new Resolvers());
context.Setup(t => t.ScopedContextData).Returns(contextData);
string result = (string)await resolver.Resolver(context.Object);
Assert.Equal("bar", result);
}

[Fact]
public async Task Compile_Arguments_ScopedContextData_DefaultValue()
{
// arrange
Type type = typeof(Resolvers);
MemberInfo resolverMember =
type.GetMethod("ResolveWithScopedContextDataDefault");
var resolverDescriptor = new ResolverDescriptor(
type,
new FieldMember("A", "b", resolverMember));
var contextData = ImmutableDictionary<string, object>.Empty;

// act
var compiler = new ResolverCompiler();
FieldResolver resolver = compiler.Compile(resolverDescriptor);

// assert
var context = new Mock<IResolverContext>();
context.Setup(t => t.Parent<Resolvers>()).Returns(new Resolvers());
context.Setup(t => t.ScopedContextData).Returns(contextData);
object result = await resolver.Resolver(context.Object);
Assert.Null(result);
}

[Fact]
public void Compile_Arguments_ScopedContextData_NotExists()
{
// arrange
Type type = typeof(Resolvers);
MemberInfo resolverMember =
type.GetMethod("ResolveWithScopedContextData");
var resolverDescriptor = new ResolverDescriptor(
type,
new FieldMember("A", "b", resolverMember));
var contextData = ImmutableDictionary<string, object>.Empty;

// act
var compiler = new ResolverCompiler();
FieldResolver resolver = compiler.Compile(resolverDescriptor);

// assert
var context = new Mock<IResolverContext>();
context.Setup(t => t.Parent<Resolvers>()).Returns(new Resolvers());
context.Setup(t => t.ScopedContextData).Returns(contextData);
Action action = () => resolver.Resolver(context.Object);
Assert.Throws<ArgumentException>(action);
}


public class Resolvers
{
public Task<object> ObjectTaskResolver() =>
Expand Down Expand Up @@ -806,6 +951,19 @@ public bool ResolverWithSchema(
public bool ResolverWithService(
[Service]MyService service) =>
service != null;

public string ResolveWithContextData(
[State("foo")]string s) => s;

public string ResolveWithContextDataDefault(
[State("foo", DefaultIfNotExists = true)]string s) => s;

public string ResolveWithScopedContextData(
[State("foo", IsScoped = true)]string s) => s;

public string ResolveWithScopedContextDataDefault(
[State("foo", IsScoped = true, DefaultIfNotExists = true)]
string s) => s;
}

public class Entity { }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using System;
using System.Linq.Expressions;
using System.Reflection;

namespace HotChocolate.Resolvers.Expressions.Parameters
{
internal sealed class GetCustomContextCompiler<T>
: ResolverParameterCompilerBase<T>
where T : IResolverContext
{
private static readonly MethodInfo _resolveContextData =
typeof(ExpressionHelper).GetMethod("ResolveContextData");
private static readonly MethodInfo _resolveScopedContextData =
typeof(ExpressionHelper).GetMethod("ResolveScopedContextData");

private readonly PropertyInfo _contextData;
private readonly PropertyInfo _scopedContextData;

public GetCustomContextCompiler()
{
_contextData = typeof(IHasContextData)
.GetTypeInfo().GetDeclaredProperty(
nameof(IResolverContext.ContextData));
_scopedContextData = ContextTypeInfo.GetDeclaredProperty(
nameof(IResolverContext.ScopedContextData));
}

public override bool CanHandle(
ParameterInfo parameter,
Type sourceType) =>
parameter.IsDefined(typeof(StateAttribute));

public override Expression Compile(
Expression context,
ParameterInfo parameter,
Type sourceType)
{
StateAttribute attribute =
parameter.GetCustomAttribute<StateAttribute>();

ConstantExpression key =
Expression.Constant(attribute.Key);

ConstantExpression defaultIfNotExists =
Expression.Constant(attribute.DefaultIfNotExists);

MemberExpression contextData = attribute.IsScoped
? Expression.Property(context, _scopedContextData)
: Expression.Property(context, _contextData);

MethodInfo resolveContextData = attribute.IsScoped
? _resolveScopedContextData.MakeGenericMethod(
parameter.ParameterType)
: _resolveContextData.MakeGenericMethod(
parameter.ParameterType);

return Expression.Call(
resolveContextData,
contextData,
key,
defaultIfNotExists);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace HotChocolate.Resolvers.Expressions.Parameters
{
internal static class ParameterCompilerFactory
{
public static IEnumerable<IResolverParameterCompiler> CreateForResolverContext()
public static IEnumerable<IResolverParameterCompiler> Create()
{
return CreateFor<IResolverContext>();
}
Expand All @@ -14,6 +14,7 @@ private static IEnumerable<IResolverParameterCompiler> CreateFor<T>()
{
yield return new GetCancellationTokenCompiler<T>();
yield return new GetContextCompiler<T, IResolverContext>();
yield return new GetCustomContextCompiler<T>();
yield return new GetDataLoaderCompiler<T>();
yield return new GetEventMessageCompiler<T>();
yield return new GetFieldSelectionCompiler<T>();
Expand Down
56 changes: 55 additions & 1 deletion src/Core/Types/Resolvers/Expressions/ResolverCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ internal sealed class ResolverCompiler
private readonly MethodInfo _taskResult;

public ResolverCompiler()
: this(ParameterCompilerFactory.CreateForResolverContext())
: this(ParameterCompilerFactory.Create())
{
}

Expand Down Expand Up @@ -185,5 +185,59 @@ public static Task<object> WrapResultHelper<T>(T result)
{
return Task.FromResult<object>(result);
}

public static TContextData ResolveContextData<TContextData>(
IDictionary<string, object> contextData,
string key,
bool defaultIfNotExists)
{
if (contextData.TryGetValue(key, out object value))
{
if (value is null)
{
return default;
}

if (value is TContextData v)
{
return v;
}
}
else if (defaultIfNotExists)
{
return default;
}

// TODO : resources
throw new ArgumentException(
"The specified context key does not exist.");
}

public static TContextData ResolveScopedContextData<TContextData>(
IReadOnlyDictionary<string, object> contextData,
string key,
bool defaultIfNotExists)
{
if (contextData.TryGetValue(key, out object value))
{
if (value is null)
{
return default;
}

if (value is TContextData v)
{
return v;
}
}
else if (defaultIfNotExists)
{
return default;
}

// TODO : resources
throw new ArgumentException(
"The specified context key does not exist.");
}
}
}

0 comments on commit 3b95fd0

Please sign in to comment.