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]: add delegate subscribers #1592

Merged
merged 1 commit into from
Mar 25, 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
2 changes: 1 addition & 1 deletion dev/GraphQL.Dev.Reviews/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

// configure services
builder.AddTankaGraphQL3()
.AddSchema("reviews", options =>
.AddOptions("reviews", options =>
{
options.AddReviews();

Expand Down
10 changes: 5 additions & 5 deletions dev/graphql.dev.chat.data/ChatResolvers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,34 +12,34 @@ public static class ChatSchemaConfigurationExtensions
{
public static ExecutableSchemaBuilder AddChat(this ExecutableSchemaBuilder builder)
{
builder.Object("Query", new Dictionary<FieldDefinition, Action<ResolverBuilder>>()
builder.Add("Query", new ()
{
{ "messages: [Message!]!", b => b.Run(r => r.GetRequiredService<IChatResolverService>().GetMessagesAsync(r)) }
});

builder.Object("Mutation", new Dictionary<FieldDefinition, Action<ResolverBuilder>>()
builder.Add("Mutation", new ()
{
{ "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.Object("Subscription", new Dictionary<FieldDefinition, Action<ResolverBuilder>>()
builder.Add("Subscription", new ()
{
{ "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.Object("Message", new Dictionary<FieldDefinition, Action<ResolverBuilder>>()
builder.Add("Message", new()
{
{ "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.Object("From", new Dictionary<FieldDefinition, Action<ResolverBuilder>>()
builder.Add("From", new ()
{
{ "userId: String!", context => context.ResolveAsPropertyOf<From>(f => f.UserId) },
{ "name: String!", context => context.ResolveAsPropertyOf<From>(f => f.Name) }
Expand Down
2 changes: 1 addition & 1 deletion dev/graphql.dev.chat.web/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
});
// configure services
builder.AddTankaGraphQL3()
.AddSchema("chat", options => { options.Configure(schema => schema.AddChat()); })
.AddOptions("chat", options => { options.Configure(schema => schema.AddChat()); })
.AddHttp()
.AddWebSockets();

Expand Down
57 changes: 18 additions & 39 deletions samples/GraphQL.Samples.Http/Program.cs
Original file line number Diff line number Diff line change
@@ -1,56 +1,41 @@
using System.Runtime.CompilerServices;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Http.Json;
using Tanka.GraphQL.Executable;
using Tanka.GraphQL.Fields;
using Tanka.GraphQL.Language.Nodes.TypeSystem;
using Tanka.GraphQL.Server;
using Tanka.GraphQL.Server.WebSockets;
using Tanka.GraphQL.ValueResolution;

var builder = WebApplication.CreateBuilder(args);

builder.Services.Configure<JsonOptions>(json =>
{
json.SerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
});
WebApplicationBuilder? builder = WebApplication.CreateBuilder(args);

// configure services
builder.AddTankaGraphQL3()
.AddSchema("schemaName", schema =>
{
schema.Configure(b =>
schema.Add("Query", new FieldsWithResolvers
{
b.Object("Query", new Dictionary<FieldDefinition, Delegate>()
{
{ "system: System!", () => new {} }
});
{ "system: System!", () => new { } }
});

b.Object("System", new Dictionary<FieldDefinition, Delegate>()
{
{ "version: String!", () => "3.0" }
});
schema.Add("System", new FieldsWithResolvers
{
{ "version: String!", () => "3.0" }
});

b.Object("Subscription", new Dictionary<FieldDefinition, Delegate>()
schema.Add("Subscription", new FieldsWithResolvers
{
{ "counter: Int!", (int objectValue) => objectValue }
},
new()
},
new FieldsWithSubscribers
{
{ "counter(to: Int!): Int!", r => r.Run((c, ct) =>
{
c.ResolvedValue = Wrap(Count(c.GetArgument<int>("to"), ct));
return default;
})}
"counter(to: Int!): Int!",
(SubscriberContext c, CancellationToken ct) => Count(c.GetArgument<int>("to"), ct)
}
});

});
})
.AddHttp()
.AddWebSockets()
//.AddSignalR()
;
.AddWebSockets();

var app = builder.Build();
WebApplication? app = builder.Build();

app.UseWebSockets();

Expand All @@ -66,15 +51,9 @@

app.Run();

static async IAsyncEnumerable<object?> Wrap<T>(IAsyncEnumerable<T> source)
{
await foreach (var o in source)
yield return o;
}

static async IAsyncEnumerable<int> Count(int to, [EnumeratorCancellation] CancellationToken cancellationToken)
{
int i = 0;
var i = 0;
while (!cancellationToken.IsCancellationRequested)
{
yield return ++i;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ public static ExecutableSchemaBuilder AddSubgraph(
SubgraphOptions options)
{
builder.Add(new SubgraphConfiguration(options));
builder.Add("_Any", new AnyScalarConverter());
builder.Add("_FieldSet", new FieldSetScalarConverter());
builder.AddConverter("_Any", new AnyScalarConverter());
builder.AddConverter("_FieldSet", new FieldSetScalarConverter());

return builder;
}
Expand Down
95 changes: 36 additions & 59 deletions src/GraphQL/Executable/ExecutableSchemaBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,106 +7,83 @@ namespace Tanka.GraphQL.Executable;

public class ExecutableSchemaBuilder
{
public List<IExecutableSchemaConfiguration> Configurations { get; } = new();
public SchemaBuilder Schema { get; }

public List<TypeSystemDocument> Documents { get; } = new();
public ResolversBuilder Resolvers { get; }

public Dictionary<string, IValueConverter> ValueConverters { get; } = new()
{
[Scalars.String.Name] = new StringConverter(),
[Scalars.Int.Name] = new IntConverter(),
[Scalars.Float.Name] = new DoubleConverter(),
[Scalars.Boolean.Name] = new BooleanConverter(),
[Scalars.ID.Name] = new IdConverter()
};
public ValueConvertersBuilder ValueConverters { get; }

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

public ExecutableSchemaBuilder Add(
IExecutableSchemaConfiguration configuration)
public ExecutableSchemaBuilder()
{
Configurations.Add(configuration);
Schema = new SchemaBuilder();
Resolvers = new ResolversBuilder();
ValueConverters = new ValueConvertersBuilder()
.AddDefaults();

return this;
DirectiveVisitorFactories = new Dictionary<string, CreateDirectiveVisitor>();
}

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

Schema.Add(document);
return this;
}

public ExecutableSchemaBuilder Object(
string type,
Dictionary<FieldDefinition, Action<ResolverBuilder>> resolverFields,
Dictionary<FieldDefinition, Action<SubscriberBuilder>>? subscriberFields = null)
public ExecutableSchemaBuilder Add(IExecutableSchemaConfiguration configuration)
{
Configurations.Add(new ObjectResolversConfiguration(type, resolverFields));

if (subscriberFields is not null)
Configurations.Add(new ObjectSubscribersConfiguration(type, subscriberFields));

configuration.Configure(Schema, Resolvers);
return this;
}

public ExecutableSchemaBuilder Object(
string type,
Dictionary<FieldDefinition, Delegate> resolverFields,
Dictionary<FieldDefinition, Action<SubscriberBuilder>>? subscriberFields = null)
public ExecutableSchemaBuilder Add(IResolverMap resolverMap)
{
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));

Add(new ResolversConfiguration(resolverMap));
return this;
}

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

public ExecutableSchemaBuilder Add(string type, CreateDirectiveVisitor visitor)
public ExecutableSchemaBuilder Add(
string typeName,
FieldsWithResolvers fields,
FieldsWithSubscribers? subscribers = null)
{
DirectiveVisitorFactories[type] = visitor;
Add(new ObjectResolversConfiguration(typeName, fields));

if (subscribers != null)
Add(new ObjectSubscribersConfiguration(typeName, subscribers));

return this;
}

public ExecutableSchemaBuilder Add(IResolverMap resolversMap)
public ExecutableSchemaBuilder AddConverter(
string typeName,
IValueConverter valueConverter)
{
Add(new ResolversConfiguration(resolversMap));
ValueConverters.Add(typeName, valueConverter);
return this;
}

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

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

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

var buildOptions = new SchemaBuildOptions
{
Resolvers = resolversBuilder.BuildResolvers(),
Subscribers = resolversBuilder.BuildSubscribers(),
ValueConverters = ValueConverters,
Resolvers = Resolvers.BuildResolvers(),
Subscribers = Resolvers.BuildSubscribers(),
ValueConverters = ValueConverters.Build(),
DirectiveVisitorFactories = DirectiveVisitorFactories.ToDictionary(kv => kv.Key, kv => kv.Value),
BuildTypesFromOrphanedExtensions = true
};


configureBuildOptions?.Invoke(buildOptions);

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

return schema;
}
Expand Down
15 changes: 15 additions & 0 deletions src/GraphQL/Executable/FieldsWithResolvers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Tanka.GraphQL.Language.Nodes.TypeSystem;
using Tanka.GraphQL.ValueResolution;

namespace Tanka.GraphQL.Executable;

public class FieldsWithResolvers : Dictionary<FieldDefinition, Action<ResolverBuilder>>
{
public void Add(FieldDefinition fieldDefinition, Delegate resolver)
{
if (!TryAdd(fieldDefinition, b => b.Run(resolver)))
{
throw new InvalidOperationException($"{fieldDefinition} already has an resolver");
}
}
}
15 changes: 15 additions & 0 deletions src/GraphQL/Executable/FieldsWithSubscribers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Tanka.GraphQL.Language.Nodes.TypeSystem;
using Tanka.GraphQL.ValueResolution;

namespace Tanka.GraphQL.Executable;

public class FieldsWithSubscribers : Dictionary<FieldDefinition, Action<SubscriberBuilder>>
{
public void Add(FieldDefinition fieldDefinition, Delegate subscriber)
{
if (!TryAdd(fieldDefinition, b => b.Run(subscriber)))
{
throw new InvalidOperationException($"{fieldDefinition} already has an subscriber");
}
}
}
30 changes: 30 additions & 0 deletions src/GraphQL/Executable/ValueConvertersBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using Tanka.GraphQL.ValueSerialization;

namespace Tanka.GraphQL.Executable;

public class ValueConvertersBuilder
{
public Dictionary<string, IValueConverter> ValueConverters { get; } = new();

public ValueConvertersBuilder AddDefaults()
{
ValueConverters.Add("Int", new IntConverter());
ValueConverters.Add("Float", new DoubleConverter());
ValueConverters.Add("String", new StringConverter());
ValueConverters.Add("Boolean", new BooleanConverter());
ValueConverters.Add("ID", new IdConverter());

return this;
}

public ValueConvertersBuilder Add(string type, IValueConverter converter)
{
ValueConverters.Add(type, converter);
return this;
}

public IReadOnlyDictionary<string, IValueConverter> Build()
{
return ValueConverters;
}
}
2 changes: 1 addition & 1 deletion src/GraphQL/Validation/ExecutionRules.cs
Original file line number Diff line number Diff line change
Expand Up @@ -984,7 +984,7 @@ void IsValidScalar(
try
{
var converter = context.Schema.GetValueConverter(type.Name) ?? throw new ValueCoercionException(
$"Value converter for '{Printer.Print(type)}' not found from schema.",
$"Value converter for '{type.Name}' not found from schema.",
type,
type);

Expand Down
Loading