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);
        }
    }
}