Resolvers
Resolving values is done with two specialized delegates. One is used resolving values and one for subscribing to streams when using subscriptions.
Resolve fields
using System.Threading.Tasks;
namespace Tanka.GraphQL.ValueResolution
{
public delegate ValueTask<IResolverResult> Resolver(IResolverContext context);
}
Resolve subscription event streams
using System.Threading;
using System.Threading.Tasks;
namespace Tanka.GraphQL.ValueResolution
{
public delegate ValueTask<ISubscriberResult> Subscriber(IResolverContext context, CancellationToken unsubscribe);
}
Resolver
When executing query or mutation Resolver
is used to resolve the value of the field. Resolver can use the context to access field arguments, schema, and other details about the context of the execution.
Subscriber
When executing subscription the Subscriber
is used to resolve the event stream to subscribe into. Both Subscriber
and Resolver
are required for field when using subscriptions.
Subscriber
is responsible for resolving the source stream part of the Specification.
Resolver
is responsible for resolving the source stream values Specification
Unsubscribe
Subscriber
is given a cancellation token unsubscribe
which will change into cancelled state when the request is unsubscribed Specification.
Building resolvers with fields
Resolvers can be configured when creating fields. This configuration is used to build the actual resolver when Schema
is built.
[Fact]
public async Task Create_Field_Resolver()
{
/* Given */
var builder = new SchemaBuilder();
builder.Query(out var query)
.Connections(connections =>
{
connections.Field(query, "field1", ScalarType.String,
"test field",
resolve => resolve.Run(context => new ValueTask<IResolverResult>(Resolve.As(42))));
});
/* When */
var sut = builder.Build();
/* Then */
var result = await sut.GetResolver(query.Name, "field1")(null);
Assert.Equal(42, result.Value);
}
[Fact]
public async Task Create_Field_Subscriber()
{
/* Given */
var builder = new SchemaBuilder();
builder.Query(out var query)
.Connections(connections =>
{
connections.Field(query, "field1", ScalarType.String,
"test field",
resolve => resolve.Run(context =>
ResolveSync.As(42)),
subscribe => subscribe.Run((context, unsubscribe) =>
ResolveSync.Subscribe(new EventChannel<object>(), unsubscribe)));
});
/* When */
var sut = builder.Build();
/* Then */
var result = await sut.GetSubscriber(query.Name, "field1")(null, CancellationToken.None);
Assert.NotNull(result.Reader);
}
Resolver middlwares can be used to build an execution chain.
Middlwares are implemented as a delegate method taking in the context and delegate for the next middlware to execute. Last link in the chain is usually the actual resolver but chain can be also interrupted before it by returning a result from the middleware.
Signature of the value resolver middlware:
using System.Threading.Tasks;
namespace Tanka.GraphQL.ValueResolution
{
public delegate ValueTask<IResolverResult> ResolverMiddleware(IResolverContext context, Resolver next);
}
Signature of the subscription middlware:
using System.Threading;
using System.Threading.Tasks;
namespace Tanka.GraphQL.ValueResolution
{
public delegate ValueTask<ISubscriberResult> SubscriberMiddleware(IResolverContext context, CancellationToken unsubscribe, Subscriber next);
}
Using resolver and subscriber maps
In some cases it's useful to be able to build the resolvers separately from the schema building. For that purpose SchemaTools
provide a method to bind resolvers to fields by using IResolverMap
and ISubscriberMap
.
[Fact]
public async Task Make_executable_schema()
{
/* Given */
var builder = new SchemaBuilder()
.Sdl(@"
type Query {
field1: Int!
}
schema {
query: Query
}
"
);
var resolvers = new ObjectTypeMap
{
{
"Query", new FieldResolversMap
{
{
"field1", async context =>
{
await Task.Delay(1);
return Resolve.As(1);
}
}
}
}
};
/* When */
var executableSchema = SchemaTools.MakeExecutableSchema(
builder: builder,
resolvers: resolvers,
subscribers: null,
converters: null,
directives: null);
/* Then */
var result = await Executor.ExecuteAsync(
new ExecutionOptions
{
Document = Parser.ParseDocument(@"{ field1 }"),
Schema = executableSchema
});
result.ShouldMatchJson(
@"{
""data"": {
""field1"": 1
}
}");
}
Dictionary based implementation is provided for setting up both resolvers and subscribers but other implementations can be easily provided.
using System.Collections.Generic;
using Tanka.GraphQL.TypeSystem;
using Tanka.GraphQL.ValueResolution;
namespace Tanka.GraphQL
{
public interface IResolverMap
{
Resolver GetResolver(string typeName, string fieldName);
}
public static class ResolverMapExtensions
{
public static Resolver GetResolver(this IResolverMap map, ComplexType type, KeyValuePair<string, IField> field)
{
return map.GetResolver(type.Name, field.Key);
}
}
}
using System.Collections.Generic;
using Tanka.GraphQL.ValueResolution;
using Tanka.GraphQL.TypeSystem;
namespace Tanka.GraphQL
{
public interface ISubscriberMap
{
Subscriber GetSubscriber(string typeName, string fieldName);
}
public static class SubscriberMapExtensions
{
public static Subscriber GetSubscriber(this ISubscriberMap map, ComplexType type,
KeyValuePair<string, IField> field)
{
return map.GetSubscriber(type.Name, field.Key);
}
}
}