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

[resolvers]: Delegates as resolvers #1584

Merged
merged 6 commits into from
Mar 19, 2023
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: 2 additions & 2 deletions dev/GraphQL.Dev.Reviews/SchemaOptionsBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -28,7 +28,7 @@ type Product @key(fields: "upc") @extends {
}
""");

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

return options;
Expand Down
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;
Expand All @@ -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!
}
Expand Down
15 changes: 8 additions & 7 deletions samples/GraphQL.Samples.Http/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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) =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
4 changes: 2 additions & 2 deletions src/GraphQL/DefaultOperationPipelineBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
43 changes: 29 additions & 14 deletions src/GraphQL/Executable/ExecutableSchemaBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
{
Expand All @@ -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
Expand Up @@ -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);
Expand Down
Loading