Skip to content

Commit

Permalink
[resolvers]: Delegates as resolvers (#1584)
Browse files Browse the repository at this point in the history
+ Add Delegate as resolver
+ Delegate can take ResolverContext argument
+ Delegate can take any property of ResolverContext as argument (by name, ignores case)
+ Delegate can take additional arguments which are resolved from ResolverContext.RequestServices
+ Delegate return value can be void, ValueTask, Task
+ Delegate return value can be T, ValueTask<T>, Task<T> and the returned value is assigned to ResolverContext.ResolvedValue automatically
pekkah authored Mar 19, 2023
1 parent a2c3ea9 commit 6398415
Showing 24 changed files with 825 additions and 61 deletions.
4 changes: 2 additions & 2 deletions dev/GraphQL.Dev.Reviews/SchemaOptionsBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@ public static SchemaOptionsBuilder AddReviews(this SchemaOptionsBuilder options)
{
options.Configure<ReviewsResolvers>((schema, resolvers) =>
{
schema.AddTypeSystem("""
schema.Add("""
type Review @key(fields: "id") {
id: ID!
body: String
@@ -28,7 +28,7 @@ type Product @key(fields: "upc") @extends {
}
""");

schema.AddResolvers(resolvers);
schema.Add(resolvers);
});

return options;
17 changes: 10 additions & 7 deletions dev/graphql.dev.chat.data/ChatResolvers.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using Tanka.GraphQL.Executable;
using System;
using System.Collections.Generic;
using Tanka.GraphQL.Executable;
using Tanka.GraphQL.Language.Nodes.TypeSystem;
using Tanka.GraphQL.Samples.Chat.Data.Domain;
using Tanka.GraphQL.Server;
using Tanka.GraphQL.ValueResolution;
@@ -9,40 +12,40 @@ public static class ChatSchemaConfigurationExtensions
{
public static ExecutableSchemaBuilder AddChat(this ExecutableSchemaBuilder builder)
{
builder.ConfigureObject("Query", new()
builder.Object("Query", new Dictionary<FieldDefinition, Action<ResolverBuilder>>()
{
{ "messages: [Message!]!", b => b.Run(r => r.GetRequiredService<IChatResolverService>().GetMessagesAsync(r)) }
});

builder.ConfigureObject("Mutation", new()
builder.Object("Mutation", new Dictionary<FieldDefinition, Action<ResolverBuilder>>()
{
{ "addMessage(message: InputMessage!): Message!", b => b.Run(r => r.GetRequiredService<IChatResolverService>().AddMessageAsync(r)) },
{ "editMessage(id: String!, message: InputMessage!): Message", b => b.Run(r => r.GetRequiredService<IChatResolverService>().EditMessageAsync(r)) }
});

builder.ConfigureObject("Subscription", new()
builder.Object("Subscription", new Dictionary<FieldDefinition, Action<ResolverBuilder>>()
{
{ "messages: Message!", b => b.Run(r => r.GetRequiredService<IChatResolverService>().ResolveMessageAsync(r)) }
}, new()
{
{ "messages: Message!", b => b.Run((r, ct) => r.GetRequiredService<IChatResolverService>().StreamMessagesAsync(r, ct)) }
});

builder.ConfigureObject("Message", new()
builder.Object("Message", new Dictionary<FieldDefinition, Action<ResolverBuilder>>()
{
{ "id: String!", context => context.ResolveAsPropertyOf<Message>(m => m.Id) },
{ "from: From!", context => context.ResolveAsPropertyOf<Message>(m => m.From) },
{ "content: String!", context => context.ResolveAsPropertyOf<Message>(m => m.Content) },
{ "timestamp: String!", context => context.ResolveAsPropertyOf<Message>(m => m.Timestamp) }
});

builder.ConfigureObject("From", new()
builder.Object("From", new Dictionary<FieldDefinition, Action<ResolverBuilder>>()
{
{ "userId: String!", context => context.ResolveAsPropertyOf<From>(f => f.UserId) },
{ "name: String!", context => context.ResolveAsPropertyOf<From>(f => f.Name) }
});

builder.AddTypeSystem("""
builder.Add("""
input InputMessage {
content: String!
}
15 changes: 8 additions & 7 deletions samples/GraphQL.Samples.Http/Program.cs
Original file line number Diff line number Diff line change
@@ -20,20 +20,21 @@
{
schema.Configure(b =>
{
b.ConfigureObject("Query", new()
b.Object("Query", new Dictionary<FieldDefinition, Delegate>()
{
{ "system: System!", context => context.ResolveAs<object>(new { }) }
{ "system: System!", () => new {} }
});

b.ConfigureObject("System", new()
b.Object("System", new Dictionary<FieldDefinition, Delegate>()
{
{ "version: String!", context => context.ResolveAs("3.0") }
{ "version: String!", () => "3.0" }
});

b.ConfigureObject("Subscription", new Dictionary<FieldDefinition, Action<ResolverBuilder>>()
b.Object("Subscription", new Dictionary<FieldDefinition, Delegate>()
{
{ "counter: Int!", r => r.Run(c => c.ResolveAs(c.ObjectValue)) }
}, new()
{ "counter: Int!", (int objectValue) => objectValue }
},
new()
{
{ "counter(to: Int!): Int!", r => r.Run((c, ct) =>
{
Original file line number Diff line number Diff line change
@@ -8,9 +8,9 @@ public static ExecutableSchemaBuilder AddSubgraph(
this ExecutableSchemaBuilder builder,
SubgraphOptions options)
{
builder.AddConfiguration(new SubgraphConfiguration(options));
builder.AddValueConverter("_Any", new AnyScalarConverter());
builder.AddValueConverter("_FieldSet", new FieldSetScalarConverter());
builder.Add(new SubgraphConfiguration(options));
builder.Add("_Any", new AnyScalarConverter());
builder.Add("_FieldSet", new FieldSetScalarConverter());

return builder;
}
4 changes: 2 additions & 2 deletions src/GraphQL/DefaultOperationPipelineBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -13,15 +13,15 @@ public static class DefaultOperationDelegateBuilderExtensions
public static OperationDelegateBuilder AddDefaultFeatures(
this OperationDelegateBuilder builder)
{
var errorFeature = new ConcurrentBagErrorCollectorFeature();
var argumentBinderFeature = new ArgumentBinderFeature();
var defaultSelectionSetExecutorFeature = new DefaultSelectionSetExecutorFeature();
var fieldExecutorFeature = new FieldExecutorFeature();
var valueCompletionFeature = new ValueCompletionFeature();

return builder.Use(next => context =>
{
context.Features.Set<IErrorCollectorFeature>(errorFeature);
// errors use state
context.Features.Set<IErrorCollectorFeature>(new ConcurrentBagErrorCollectorFeature());
context.Features.Set<IArgumentBinderFeature>(argumentBinderFeature);
context.Features.Set<ISelectionSetExecutorFeature>(defaultSelectionSetExecutorFeature);
context.Features.Set<IFieldExecutorFeature>(fieldExecutorFeature);
43 changes: 29 additions & 14 deletions src/GraphQL/Executable/ExecutableSchemaBuilder.cs
Original file line number Diff line number Diff line change
@@ -22,23 +22,23 @@ public class ExecutableSchemaBuilder

public Dictionary<string, CreateDirectiveVisitor> DirectiveVisitorFactories { get; } = new();

public ExecutableSchemaBuilder AddConfiguration(
public ExecutableSchemaBuilder Add(
IExecutableSchemaConfiguration configuration)
{
Configurations.Add(configuration);

return this;
}

public ExecutableSchemaBuilder AddTypeSystem(
public ExecutableSchemaBuilder Add(
TypeSystemDocument document)
{
Documents.Add(document);

return this;
}

public ExecutableSchemaBuilder ConfigureObject(
public ExecutableSchemaBuilder Object(
string type,
Dictionary<FieldDefinition, Action<ResolverBuilder>> resolverFields,
Dictionary<FieldDefinition, Action<SubscriberBuilder>>? subscriberFields = null)
@@ -51,27 +51,48 @@ public ExecutableSchemaBuilder ConfigureObject(
return this;
}

public ExecutableSchemaBuilder AddValueConverter(string type, IValueConverter converter)
public ExecutableSchemaBuilder Object(
string type,
Dictionary<FieldDefinition, Delegate> resolverFields,
Dictionary<FieldDefinition, Action<SubscriberBuilder>>? subscriberFields = null)
{
Configurations.Add(new ObjectDelegateResolversConfiguration(type, resolverFields));

if (subscriberFields is not null)
//todo: subscriber fields should be delegates as well
Configurations.Add(new ObjectSubscribersConfiguration(type, subscriberFields));

return this;
}

public ExecutableSchemaBuilder Add(string type, IValueConverter converter)
{
ValueConverters[type] = converter;
return this;
}

public ExecutableSchemaBuilder AddDirectiveVisitor(string type, CreateDirectiveVisitor visitor)
public ExecutableSchemaBuilder Add(string type, CreateDirectiveVisitor visitor)
{
DirectiveVisitorFactories[type] = visitor;
return this;
}

public ExecutableSchemaBuilder Add(IResolverMap resolversMap)
{
Add(new ResolversConfiguration(resolversMap));
return this;
}

public async Task<ISchema> Build(Action<SchemaBuildOptions>? configureBuildOptions = null)
{
var schemaBuilder = new SchemaBuilder();
var resolversBuilder = new ResolversBuilder();

foreach (var typeSystemDocument in Documents)
foreach (TypeSystemDocument typeSystemDocument in Documents)
schemaBuilder.Add(typeSystemDocument);

foreach (var configuration in Configurations) await configuration.Configure(schemaBuilder, resolversBuilder);
foreach (IExecutableSchemaConfiguration configuration in Configurations)
await configuration.Configure(schemaBuilder, resolversBuilder);

var buildOptions = new SchemaBuildOptions
{
@@ -85,14 +106,8 @@ public async Task<ISchema> Build(Action<SchemaBuildOptions>? configureBuildOptio

configureBuildOptions?.Invoke(buildOptions);

var schema = await schemaBuilder.Build(buildOptions);
ISchema schema = await schemaBuilder.Build(buildOptions);

return schema;
}

public ExecutableSchemaBuilder AddResolvers(IResolverMap resolversMap)
{
AddConfiguration(new ResolversConfiguration(resolversMap));
return this;
}
}
47 changes: 47 additions & 0 deletions src/GraphQL/Executable/ObjectDelegateResolversConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using Tanka.GraphQL.Language.Nodes.TypeSystem;
using Tanka.GraphQL.ValueResolution;

namespace Tanka.GraphQL.Executable;

public class ObjectDelegateResolversConfiguration : IExecutableSchemaConfiguration
{
public ObjectDelegateResolversConfiguration(
string type,
IReadOnlyDictionary<FieldDefinition, Delegate> fields)
{
Type = type;
Fields = fields;
}

public string Type { get; }

public IReadOnlyDictionary<FieldDefinition, Delegate> Fields { get; }

public Task Configure(SchemaBuilder schema, ResolversBuilder resolvers)
{
return Task.WhenAll(Configure(schema), Configure(resolvers));
}

private Task Configure(ResolversBuilder builder)
{
foreach (var (field, resolverDelegate) in Fields)
builder.Resolver(Type, field.Name.Value).Run(resolverDelegate);

return Task.CompletedTask;
}

private Task Configure(SchemaBuilder builder)
{
var fields = Fields.Select(kv => kv.Key).ToList();

// we add as type extension so we don't hit any conflicts with type names
// WARNING: if schema is built with BuildTypesFromOrphanedExtensions set to false
// the build will fail
builder.Add(new TypeExtension(new ObjectDefinition(
null,
Type,
fields: new(fields))));

return Task.CompletedTask;
}
}
3 changes: 3 additions & 0 deletions src/GraphQL/QueryContext.cs
Original file line number Diff line number Diff line change
@@ -95,6 +95,9 @@ public IAsyncEnumerable<ExecutionResult> Response
set => ResponseFeature.Response = value;
}

//todo: turn into a feature
public IServiceProvider RequestServices { get; set; }

public void AddError(Exception x)
{
ArgumentNullException.ThrowIfNull(ErrorCollectorFeature);
178 changes: 178 additions & 0 deletions src/GraphQL/ValueResolution/DelegateResolverFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;

namespace Tanka.GraphQL.ValueResolution;

public static class DelegateResolverFactory
{
private static readonly ParameterExpression ContextParam = Expression.Parameter(typeof(ResolverContext), "context");

private static readonly IReadOnlyDictionary<string, Expression> ContextParamProperties = typeof(ResolverContext)
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.ToDictionary(p => p.Name.ToLowerInvariant(), p => (Expression)Expression.Property(ContextParam, p));


private static readonly ConcurrentDictionary<Delegate, Resolver> Cache = new ConcurrentDictionary<Delegate, Resolver>();

public static Resolver GetOrCreate(Delegate resolverDelegate)
{
if (Cache.TryGetValue(resolverDelegate, out var resolver))
return resolver;

return Create(resolverDelegate);
}

public static Resolver Create(Delegate resolverDelegate)
{
#if DEBUG
Trace.WriteLine(
$"Available context parameters:\n {string.Join(',', ContextParamProperties.Select(p => string.Concat($"{p.Key}: {p.Value.Type}")))}");
#endif

MethodInfo invokeMethod = resolverDelegate.Method;

Expression instanceExpression = null;
if (!invokeMethod.IsStatic) instanceExpression = Expression.Constant(resolverDelegate.Target);

IEnumerable<Expression> argumentsExpressions = invokeMethod.GetParameters()
.Select(p =>
{
if (p.ParameterType == typeof(ResolverContext)) return ContextParam;

if (p.Name is not null)
if (ContextParamProperties.TryGetValue(p.Name.ToLowerInvariant(),
out Expression? propertyExpression))
{
if (p.ParameterType == propertyExpression.Type)
return propertyExpression;

return Expression.Convert(propertyExpression, p.ParameterType);
}

MemberExpression serviceProviderProperty = Expression.Property(ContextParam, "RequestServices");
MethodInfo? getServiceMethodInfo = typeof(ServiceProviderServiceExtensions)
.GetMethods()
.FirstOrDefault(m => m.Name == nameof(ServiceProviderServiceExtensions.GetRequiredService)
&& m.GetParameters().Length == 1
&& m.GetParameters()[0].ParameterType == typeof(IServiceProvider)
&& m.IsGenericMethodDefinition);

if (getServiceMethodInfo is null)
throw new InvalidOperationException("Could not find GetRequiredService method");

Type serviceType = p.ParameterType;
MethodInfo genericMethodInfo = getServiceMethodInfo.MakeGenericMethod(serviceType);
var getRequiredServiceCall = (Expression)Expression.Call(
null,
genericMethodInfo,
serviceProviderProperty);

return Expression.Block(
getRequiredServiceCall
);
});

MethodCallExpression invokeExpression = Expression.Call(
instanceExpression,
invokeMethod,
argumentsExpressions
);

Expression valueTaskExpression;
if (invokeMethod.ReturnType == typeof(ValueTask))
{
valueTaskExpression = invokeExpression;
}
else if (invokeMethod.ReturnType == typeof(Task))
{
valueTaskExpression = Expression.New(
typeof(ValueTask).GetConstructor(new[] { typeof(Task) })!,
invokeExpression
);
}
else if (invokeMethod.ReturnType == typeof(void))
{
valueTaskExpression = Expression.Block(
invokeExpression,
Expression.Constant(ValueTask.CompletedTask)
);
}
else if (invokeMethod.ReturnType.IsGenericType &&
invokeMethod.ReturnType.GetGenericTypeDefinition() == typeof(Task<>))
{
var t = invokeMethod.ReturnType.GetGenericArguments()[0];
valueTaskExpression = Expression.Call(ResolveValueTaskMethod.MakeGenericMethod(t), invokeExpression, ContextParam);
}
else if (invokeMethod.ReturnType.IsGenericType &&
invokeMethod.ReturnType.GetGenericTypeDefinition() == typeof(ValueTask<>))
{
var t = invokeMethod.ReturnType.GetGenericArguments()[0];
valueTaskExpression = Expression.Call(ResolveValueValueTaskMethod.MakeGenericMethod(t), invokeExpression, ContextParam);
}
else
{
var t = invokeMethod.ReturnType;
valueTaskExpression = Expression.Call(ResolveValueObjectMethod.MakeGenericMethod(t), invokeExpression, ContextParam);
}


var lambda = Expression.Lambda<Resolver>(
valueTaskExpression,
ContextParam
);

var compiledLambda = lambda.Compile();
Cache.TryAdd(resolverDelegate, compiledLambda);
return compiledLambda;
}

private static readonly MethodInfo ResolveValueTaskMethod = typeof(DelegateResolverFactory)
.GetMethod(nameof(ResolveValueTask), BindingFlags.Static | BindingFlags.NonPublic)!;

private static readonly MethodInfo ResolveValueValueTaskMethod = typeof(DelegateResolverFactory)
.GetMethod(nameof(ResolveValueValueTask), BindingFlags.Static | BindingFlags.NonPublic)!;

private static readonly MethodInfo ResolveValueObjectMethod = typeof(DelegateResolverFactory)
.GetMethod(nameof(ResolveValueObject), BindingFlags.Static | BindingFlags.NonPublic)!;

private static ValueTask ResolveValueTask<T>(Task<T> task, ResolverContext context)
{
static async ValueTask AwaitResolveValue(Task<T> task, ResolverContext context)
{
context.ResolvedValue = await task;
}

if (task.IsCompletedSuccessfully)
{
context.ResolvedValue = task.Result;
return default;
}

return AwaitResolveValue(task, context);
}

private static ValueTask ResolveValueValueTask<T>(ValueTask<T> task, ResolverContext context)
{
static async ValueTask AwaitResolveValue(ValueTask<T> task, ResolverContext context)
{
context.ResolvedValue = await task;
}

if (task.IsCompletedSuccessfully)
{
context.ResolvedValue = task.Result;
return default;
}

return AwaitResolveValue(task, context);
}

private static ValueTask ResolveValueObject<T>(T result, ResolverContext context)
{
context.ResolvedValue = result;
return ValueTask.CompletedTask;
}
}
15 changes: 15 additions & 0 deletions src/GraphQL/ValueResolution/FieldResolversMap.cs
Original file line number Diff line number Diff line change
@@ -43,6 +43,11 @@ public void Add(string key, Resolver resolver)
_resolvers.Add(key, resolver);
}

public void Add(string key, Delegate resolver)
{
_resolvers.Add(key, DelegateResolverFactory.GetOrCreate(resolver));
}

public void Add(string key, Subscriber subscriber)
{
_subscribers.Add(key, subscriber);
@@ -58,6 +63,16 @@ public void Add(string key, Subscriber subscriber, Resolver resolver)
_resolvers.Add(key, resolver);
}

public void Add(string key, Subscriber subscriber, Delegate resolver)
{
if (key == null) throw new ArgumentNullException(nameof(key));
if (subscriber == null) throw new ArgumentNullException(nameof(subscriber));
if (resolver == null) throw new ArgumentNullException(nameof(resolver));

_subscribers.Add(key, subscriber);
_resolvers.Add(key, DelegateResolverFactory.GetOrCreate(resolver));
}

public Resolver? GetResolver(string key)
{
if (!_resolvers.ContainsKey(key))
5 changes: 5 additions & 0 deletions src/GraphQL/ValueResolution/ResolverBuilder.cs
Original file line number Diff line number Diff line change
@@ -15,6 +15,11 @@ public ResolverBuilder Run(Resolver resolver)
return Use(_ => resolver);
}

public ResolverBuilder Run(Delegate resolver)
{
return Use(_ => DelegateResolverFactory.GetOrCreate(resolver));
}

public Resolver Build()
{
Resolver resolver = context => ValueTask.CompletedTask;
2 changes: 2 additions & 0 deletions src/GraphQL/ValueResolution/ResolverContextBase.cs
Original file line number Diff line number Diff line change
@@ -24,4 +24,6 @@ public class ResolverContextBase
public ISchema Schema => QueryContext.Schema;

public string FieldName => Selection.Name.Value;

public IServiceProvider RequestServices => QueryContext.RequestServices;
}
10 changes: 10 additions & 0 deletions src/GraphQL/ValueResolution/ResolversMap.cs
Original file line number Diff line number Diff line change
@@ -68,6 +68,16 @@ public void Add(string typeName, string fieldName, Resolver resolver, Subscriber
fieldsResolvers.Add(fieldName, subscriber, resolver);
}

public void Add(string typeName, string fieldName, Delegate resolver, Subscriber? subscriber = null)
{
if (!TryGetValue(typeName, out var fieldsResolvers)) fieldsResolvers = this[typeName] = new();

if (subscriber is null)
fieldsResolvers.Add(fieldName, resolver);
else
fieldsResolvers.Add(fieldName, subscriber, resolver);
}

public bool Add(string typeName, string fieldName, Subscriber subscriber)
{
if (!TryGetValue(typeName, out var fieldsResolvers)) fieldsResolvers = this[typeName] = new();
4 changes: 2 additions & 2 deletions src/graphql.server/SchemaOptionsBuilder.cs
Original file line number Diff line number Diff line change
@@ -41,13 +41,13 @@ public SchemaOptionsBuilder PostConfigure<T>(Action<ExecutableSchemaBuilder, T>

public SchemaOptionsBuilder AddConfiguration(IExecutableSchemaConfiguration configuration)
{
Builder.Configure(options => options.Builder.AddConfiguration(configuration));
Builder.Configure(options => options.Builder.Add(configuration));
return this;
}

public SchemaOptionsBuilder AddTypeSystem(TypeSystemDocument document)
{
Builder.Configure(options => options.Builder.AddTypeSystem(document));
Builder.Configure(options => options.Builder.Add(document));
return this;
}
}
Original file line number Diff line number Diff line change
@@ -18,7 +18,7 @@ public async Task EntityUnion_does_not_contain_object_without_key_directive()
{
/* Given */
var builder = new ExecutableSchemaBuilder()
.AddTypeSystem(@"
.Add(@"
type Person @key(fields: ""id"") {
id: ID!
}
@@ -43,7 +43,7 @@ public async Task EntityUnion_has_possible_type_with_key_directive()
{
/* Given */
var builder = new ExecutableSchemaBuilder()
.AddTypeSystem(@"
.Add(@"
type Person @key(fields: ""id"") {
id: ID!
}")
@@ -64,7 +64,7 @@ public async Task Query_entities()
{
/* Given */
var builder = new ExecutableSchemaBuilder()
.AddTypeSystem(@"
.Add(@"
type Person @key(fields: ""id"") {
id: ID!
name: String!
@@ -77,7 +77,7 @@ type Address @key(fields: ""street"") {
["Person"] = (context, type, representation) => new(
new ResolveReferenceResult(type, representation))
}))
.AddResolvers(new ResolversMap
.Add(new ResolversMap
{
["Person"] = new()
{
@@ -133,7 +133,7 @@ public async Task Query_sdl()
{
/* Given */
var builder = new ExecutableSchemaBuilder()
.AddTypeSystem(@"
.Add(@"
type Review @key(fields: ""id"") {
id: ID!
product: Product
Original file line number Diff line number Diff line change
@@ -37,13 +37,13 @@ type Query {
";

var builder = new ExecutableSchemaBuilder();
builder.AddTypeSystem(typeDefs);
builder.Add(typeDefs);
builder.AddSubgraph(new(new DictionaryReferenceResolversMap
{
["User"] = UserReference,
["Product"] = ProductReference
}));
builder.AddResolvers(new ResolversMap
builder.Add(new ResolversMap
{
["User"] = new()
{
2 changes: 1 addition & 1 deletion tests/GraphQL.Extensions.Tracing.Tests/TraceFacts.cs
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@ public class TraceFacts
public TraceFacts()
{
Schema = new ExecutableSchemaBuilder()
.ConfigureObject("Query", new Dictionary<FieldDefinition, Action<ResolverBuilder>>()
.Object("Query", new Dictionary<FieldDefinition, Action<ResolverBuilder>>()
{
["simple: String!"] = b => b.ResolveAs("string")
})
11 changes: 7 additions & 4 deletions tests/graphql.tests/Executor.MutationFacts.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
using System.Threading.Tasks;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Tanka.GraphQL.Executable;
using Tanka.GraphQL.Language.Nodes;
using Tanka.GraphQL.Language.Nodes.TypeSystem;
using Tanka.GraphQL.Request;
using Tanka.GraphQL.ValueResolution;
using Xunit;
@@ -14,7 +17,7 @@ public async Task Simple_Scalar()
{
/* Given */
var schema = await new ExecutableSchemaBuilder()
.ConfigureObject("Mutation", new()
.Object("Mutation", new Dictionary<FieldDefinition, Action<ResolverBuilder>>()
{
{ "version: String!", b => b.ResolveAs("1.0") }
})
@@ -48,11 +51,11 @@ public async Task Object_with_ScalarField()
{
/* Given */
var schema = await new ExecutableSchemaBuilder()
.ConfigureObject("System", new()
.Object("System", new Dictionary<FieldDefinition, Action<ResolverBuilder>>()
{
{ "version: String!", b => b.ResolveAs("1.0") }
})
.ConfigureObject("Mutation", new()
.Object("Mutation", new Dictionary<FieldDefinition, Action<ResolverBuilder>>()
{
{ "system: System!", b => b.ResolveAs("System") }
})
11 changes: 7 additions & 4 deletions tests/graphql.tests/Executor.QueryFacts.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
using System.Threading.Tasks;
using System.Collections.Generic;
using System;
using System.Threading.Tasks;
using Tanka.GraphQL.Executable;
using Tanka.GraphQL.Language.Nodes;
using Tanka.GraphQL.Language.Nodes.TypeSystem;
using Tanka.GraphQL.Request;
using Tanka.GraphQL.ValueResolution;
using Xunit;
@@ -14,7 +17,7 @@ public async Task Simple_Scalar()
{
/* Given */
var schema = await new ExecutableSchemaBuilder()
.ConfigureObject("Query", new()
.Object("Query", new Dictionary<FieldDefinition, Action<ResolverBuilder>>()
{
{ "version: String!", b => b.ResolveAs("1.0") }
})
@@ -48,11 +51,11 @@ public async Task Object_with_ScalarField()
{
/* Given */
var schema = await new ExecutableSchemaBuilder()
.ConfigureObject("System", new()
.Object("System", new Dictionary<FieldDefinition, Action<ResolverBuilder>>()
{
{ "version: String!", b => b.ResolveAs("1.0") }
})
.ConfigureObject("Query", new()
.Object("Query", new Dictionary<FieldDefinition, Action<ResolverBuilder>>()
{
{ "system: System!", b => b.ResolveAs("System") }
})
6 changes: 4 additions & 2 deletions tests/graphql.tests/Executor.SubscriptionFacts.cs
Original file line number Diff line number Diff line change
@@ -5,7 +5,9 @@
using System.Threading.Tasks;
using Tanka.GraphQL.Executable;
using Tanka.GraphQL.Language.Nodes;
using Tanka.GraphQL.Language.Nodes.TypeSystem;
using Tanka.GraphQL.Request;
using Tanka.GraphQL.ValueResolution;
using Xunit;

namespace Tanka.GraphQL.Tests;
@@ -19,8 +21,8 @@ public async Task Simple_Scalar()
{
/* Given */
var schema = await new ExecutableSchemaBuilder()
.ConfigureObject("Query", new())
.ConfigureObject("Subscription", new()
.Object("Query", new Dictionary<FieldDefinition, Action<ResolverBuilder>>())
.Object("Subscription", new Dictionary<FieldDefinition, Action<ResolverBuilder>>()
{
{
"count: Int!", b => b.Run(ctx =>
466 changes: 466 additions & 0 deletions tests/graphql.tests/ValueResolution/DelegateResolverFactoryFacts.cs

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions tests/graphql.tests/ValueResolution/IMyDependency.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Tanka.GraphQL.Tests.ValueResolution;

public interface IMyDependency
{
}

class MyDependency : IMyDependency
{
}
8 changes: 5 additions & 3 deletions tests/graphql.tests/graphql.tests.csproj
Original file line number Diff line number Diff line change
@@ -5,12 +5,14 @@
<IsPackable>false</IsPackable>
<AssemblyName>tanka.graphql.tests</AssemblyName>
<RootNamespace>Tanka.GraphQL.Tests</RootNamespace>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<Compile Remove="ValueResolution\**" />
<EmbeddedResource Remove="ValueResolution\**" />
<None Remove="ValueResolution\**" />
<Compile Remove="ValueResolution\InterfaceCompletionFacts.cs" />
<Compile Remove="ValueResolution\ResolverBuilderFacts.cs" />
<Compile Remove="ValueResolution\ResolverContextFacts.cs" />
<Compile Remove="ValueResolution\UnionCompletionFacts.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
6 changes: 3 additions & 3 deletions tutorials/graphql.tutorials.getting-started/GettingStarted.cs
Original file line number Diff line number Diff line change
@@ -60,7 +60,7 @@ type Query {
Resolvers = new ResolversMap
{
{
"Query", "name", context => context.ResolveAs("Test")
"Query", "name", () => "Test"
}
}
});
@@ -102,7 +102,7 @@ type Query {
"Query", new FieldResolversMap
{
{
"name", context => context.ResolveAs("Test")
"name", () => "Test"
}
}
}
@@ -180,7 +180,7 @@ type Query {
"Query", new FieldResolversMap
{
{
"url", context => context.ResolveAs(new Uri("https://localhost/"))
"url", () => new Uri("https://localhost/")
}
}
}

0 comments on commit 6398415

Please sign in to comment.