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

    public delegate ValueTask<IResolverResult> Resolver(IResolverContext context);

Resolve subscription event streams

    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:

    public delegate ValueTask<IResolverResult> ResolverMiddleware(IResolverContext context, Resolver next);

Signature of the subscription middlware:

    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 schema1 = new SchemaBuilder()
                .Query(out var query1)
                .Connections(connect =>
                    connect.Field(query1, "field1", ScalarType.Int)
                )
                .Build();

            var resolvers = new ObjectTypeMap
            {
                {
                    query1.Name, new FieldResolversMap
                    {
                        {
                            "field1", async context =>
                            {
                                await Task.Delay(1);
                                return Resolve.As(1);
                            }
                        }
                    }
                }
            };

            /* When */
            var executableSchema = SchemaTools.MakeExecutableSchema(
                schema: schema1,
                resolvers: resolvers,
                subscribers: 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.

    public interface IResolverMap
    {
        Resolver GetResolver(string typeName, string fieldName);
    }
    public interface ISubscriberMap
    {
        Subscriber GetSubscriber(string typeName, string fieldName);
    }