Skip to content

Commit

Permalink
Add subscription support to source generator (#1729)
Browse files Browse the repository at this point in the history
  • Loading branch information
pekkah authored Jan 24, 2024
1 parent 135ddde commit f9a801e
Show file tree
Hide file tree
Showing 36 changed files with 1,389 additions and 158 deletions.
13 changes: 13 additions & 0 deletions docs/4-code-generator/05-samples-sg-subscriptions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
## Subscriptions

Code generator will generate object definition and extensions methods for adding subscription
types to the schema. Generator will generate a subscriber for each `IAsyncEnumerable<T>` method
of a class with `[ObjectType]` attribute. You can use provided extension method to add the
subscription type, subscribers and resolver to the schema.


### Tanka.GraphQL.Samples.SG.Subscription

```csharp
#include::xref://samples:GraphQL.Samples.SG.Subscription/Program.cs
```
3 changes: 2 additions & 1 deletion docs/4-code-generator/nav.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
- [Installation & basics](xref://01-samples-sg-basic.md)
- [Arguments](xref://02-samples-sg-arguments.md)
- [Namespace](xref://03-samples-sg-namespace.md)
- [Dependency injection](xref://04-samples-sg-services.md)
- [Dependency injection](xref://04-samples-sg-services.md)
- [Subscriptions](xref://05-samples-sg-subscriptions.md)
1 change: 0 additions & 1 deletion samples/GraphQL.Samples.SG.InputType/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System.Collections.Concurrent;
using Microsoft.AspNetCore.Mvc;

using Tanka.GraphQL;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\GraphQL.Language\GraphQL.Language.csproj" />
<ProjectReference Include="..\..\src\GraphQL.Server\GraphQL.Server.csproj" />
<ProjectReference Include="..\..\src\GraphQL\GraphQL.csproj" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\GraphQL.Server.SourceGenerators\GraphQL.Server.SourceGenerators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>

</Project>
55 changes: 55 additions & 0 deletions samples/GraphQL.Samples.SG.Subscription/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System.Runtime.CompilerServices;

using Tanka.GraphQL.Server;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

builder.AddTankaGraphQL()
.AddHttp()
.AddWebSockets()
.AddSchemaOptions("Default", options =>
{
// This extension point is used by the generator to add
// type controllers
options.AddGeneratedTypes(types =>
{
// Add generated controllers
types
.AddQueryController()
.AddSubscriptionController();
});
});

WebApplication app = builder.Build();
app.UseWebSockets();

app.MapTankaGraphQL("/graphql", "Default");
app.MapGraphiQL("/graphql/ui");
app.Run();

[ObjectType]
public static class Subscription
{
/// <summary>
/// This is subscription field producing random integers of count between from and to
/// </summary>
/// <returns></returns>
public static async IAsyncEnumerable<int> Random(int from, int to, int count, [EnumeratorCancellation] CancellationToken cancellationToken)
{
var r = new Random();

for (var i = 0; i < count; i++)
{
cancellationToken.ThrowIfCancellationRequested();
yield return r.Next(from, to);
await Task.Delay(500, cancellationToken);
}
}
}

[ObjectType]
public static class Query
{
// this is required as the graphiql will error without a query field
public static string Hello() => "Hello World!";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"profiles": {
"GraphQL.Samples.SG.Subscription": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "https://localhost:7239/graphql/ui",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:7239",
"dotnetRunMessages": true
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
9 changes: 9 additions & 0 deletions samples/GraphQL.Samples.SG.Subscription/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
115 changes: 115 additions & 0 deletions src/GraphQL.Server.SourceGenerators/Internal/EquatableArray.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;

namespace Tanka.GraphQL.Server.SourceGenerators.Internal;

public static class EquatableArrayExtensions
{
public static EquatableArray<T> ToEquatableArray<T>(this IEnumerable<T> array)
where T : IEquatable<T>
{
return new(array.ToArray());

}
}

/// <summary>
/// Origin https://github.com/andrewlock/NetEscapades.EnumGenerators
/// An immutable, equatable array. This is equivalent to <see cref="Array"/> but with value equality support.
/// </summary>
/// <typeparam name="T">The type of values in the array.</typeparam>
public readonly struct EquatableArray<T> : IEquatable<EquatableArray<T>>, IEnumerable<T>
where T : IEquatable<T>
{
/// <summary>
/// The underlying <typeparamref name="T"/> array.
/// </summary>
private readonly T[]? _array;

/// <summary>
/// Creates a new <see cref="EquatableArray{T}"/> instance.
/// </summary>
/// <param name="array">The input <see cref="ImmutableArray{T}"/> to wrap.</param>
public EquatableArray(T[] array)
{
_array = array;
}

/// <sinheritdoc/>
public bool Equals(EquatableArray<T> array)
{
return AsSpan().SequenceEqual(array.AsSpan());
}

/// <sinheritdoc/>
public override bool Equals(object? obj)
{
return obj is EquatableArray<T> array && Equals(this, array);
}

/// <sinheritdoc/>
public override int GetHashCode()
{
if (_array is not T[] array)
{
return 0;
}

HashCode hashCode = default;

foreach (T item in array)
{
hashCode.Add(item);
}

return hashCode.ToHashCode();
}

/// <summary>
/// Returns a <see cref="ReadOnlySpan{T}"/> wrapping the current items.
/// </summary>
/// <returns>A <see cref="ReadOnlySpan{T}"/> wrapping the current items.</returns>
public ReadOnlySpan<T> AsSpan()
{
return _array.AsSpan();
}

/// <sinheritdoc/>
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return ((IEnumerable<T>)(_array ?? Array.Empty<T>())).GetEnumerator();
}

/// <sinheritdoc/>
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable<T>)(_array ?? Array.Empty<T>())).GetEnumerator();
}

public int Count => _array?.Length ?? 0;

/// <summary>
/// Checks whether two <see cref="EquatableArray{T}"/> values are the same.
/// </summary>
/// <param name="left">The first <see cref="EquatableArray{T}"/> value.</param>
/// <param name="right">The second <see cref="EquatableArray{T}"/> value.</param>
/// <returns>Whether <paramref name="left"/> and <paramref name="right"/> are equal.</returns>
public static bool operator ==(EquatableArray<T> left, EquatableArray<T> right)
{
return left.Equals(right);
}

/// <summary>
/// Checks whether two <see cref="EquatableArray{T}"/> values are not the same.
/// </summary>
/// <param name="left">The first <see cref="EquatableArray{T}"/> value.</param>
/// <param name="right">The second <see cref="EquatableArray{T}"/> value.</param>
/// <returns>Whether <paramref name="left"/> and <paramref name="right"/> are not equal.</returns>
public static bool operator !=(EquatableArray<T> left, EquatableArray<T> right)
{
return !left.Equals(right);
}
}
Loading

0 comments on commit f9a801e

Please sign in to comment.