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 StateAttribute support in resolver compilers. #887

Merged
merged 2 commits into from
Jul 2, 2019
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
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.");
}
}
}