Skip to content

Commit

Permalink
Add Redis Provider for Subscriptions (#902)
Browse files Browse the repository at this point in the history
  • Loading branch information
glucaci authored and michaelstaib committed Jul 19, 2019
1 parent 6e40789 commit 0cf04dd
Show file tree
Hide file tree
Showing 26 changed files with 672 additions and 25 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added prisma filter types. [#861](https://github.com/ChilliCream/hotchocolate/pull/861)
- Added UTF-8 request parser. [#869](https://github.com/ChilliCream/hotchocolate/pull/869)
- Added new syntax visitor API.
- Added Redis subscription provider [#902](https://github.com/ChilliCream/hotchocolate/pull/902)

### Changed

Expand All @@ -29,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Order of types in a serialized schema is now consistent. [#891](https://github.com/ChilliCream/hotchocolate/pull/891)
- Respect UseXmlDocumentation with Schema.Create [#897](https://github.com/ChilliCream/hotchocolate/pull/897)
- Variables now work in lists and input objects [#896](https://github.com/ChilliCream/hotchocolate/pull/896)
- Fixed url scalar now correctly detects url strings.

## [9.0.4] - 2019-06-16

Expand Down
14 changes: 14 additions & 0 deletions src/Core/Core.sln
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Types.Filters.Tests", "Type
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Types.Tests.Documentation", "Types.Tests.Documentation\Types.Tests.Documentation.csproj", "{38537BB2-BDD9-4842-BD2D-0B84B371F1D8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Subscriptions.Redis", "Subscriptions.Redis\Subscriptions.Redis.csproj", "{85660981-4992-49A8-A786-D6FB82E01463}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StarWars", "StarWars\StarWars.csproj", "{6D66B162-D2F6-411E-8F9C-F0B3AB5A9289}"
EndProject
Global
Expand Down Expand Up @@ -309,6 +311,18 @@ Global
{38537BB2-BDD9-4842-BD2D-0B84B371F1D8}.Release|x64.Build.0 = Release|Any CPU
{38537BB2-BDD9-4842-BD2D-0B84B371F1D8}.Release|x86.ActiveCfg = Release|Any CPU
{38537BB2-BDD9-4842-BD2D-0B84B371F1D8}.Release|x86.Build.0 = Release|Any CPU
{85660981-4992-49A8-A786-D6FB82E01463}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{85660981-4992-49A8-A786-D6FB82E01463}.Debug|Any CPU.Build.0 = Debug|Any CPU
{85660981-4992-49A8-A786-D6FB82E01463}.Debug|x64.ActiveCfg = Debug|Any CPU
{85660981-4992-49A8-A786-D6FB82E01463}.Debug|x64.Build.0 = Debug|Any CPU
{85660981-4992-49A8-A786-D6FB82E01463}.Debug|x86.ActiveCfg = Debug|Any CPU
{85660981-4992-49A8-A786-D6FB82E01463}.Debug|x86.Build.0 = Debug|Any CPU
{85660981-4992-49A8-A786-D6FB82E01463}.Release|Any CPU.ActiveCfg = Release|Any CPU
{85660981-4992-49A8-A786-D6FB82E01463}.Release|Any CPU.Build.0 = Release|Any CPU
{85660981-4992-49A8-A786-D6FB82E01463}.Release|x64.ActiveCfg = Release|Any CPU
{85660981-4992-49A8-A786-D6FB82E01463}.Release|x64.Build.0 = Release|Any CPU
{85660981-4992-49A8-A786-D6FB82E01463}.Release|x86.ActiveCfg = Release|Any CPU
{85660981-4992-49A8-A786-D6FB82E01463}.Release|x86.Build.0 = Release|Any CPU
{6D66B162-D2F6-411E-8F9C-F0B3AB5A9289}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6D66B162-D2F6-411E-8F9C-F0B3AB5A9289}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6D66B162-D2F6-411E-8F9C-F0B3AB5A9289}.Debug|x64.ActiveCfg = Debug|Any CPU
Expand Down
1 change: 1 addition & 0 deletions src/Core/Language/InternalsVisibleTo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
[assembly: InternalsVisibleTo("HotChocolate.Language.Tests")]
[assembly: InternalsVisibleTo("HotChocolate.Stitching")]
[assembly: InternalsVisibleTo("HotChocolate.Core")]
[assembly: InternalsVisibleTo("HotChocolate.Subscriptions")]
3 changes: 3 additions & 0 deletions src/Core/Language/Parser/ParserOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,8 @@ public ParserOptions(
public ParserOptionsExperimental Experimental { get; }

public static ParserOptions Default { get; } = new ParserOptions();

public static ParserOptions NoLocation { get; } =
new ParserOptions(noLocations: true);
}
}
29 changes: 29 additions & 0 deletions src/Core/Language/Utf8/Utf8GraphQLParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,29 @@ public Utf8GraphQLParser(
_description = null;
}

internal Utf8GraphQLParser(
Utf8GraphQLReader reader,
ParserOptions options)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}

if (reader.Kind == TokenKind.EndOfFile)
{
throw new ArgumentException(
LangResources.GraphQLData_Empty,
nameof(reader));
}

_options = options;
_createLocation = !options.NoLocations;
_allowFragmentVars = options.Experimental.AllowFragmentVariables;
_reader = reader;
_description = null;
}

public DocumentNode Parse()
{
var definitions = new List<IDefinitionNode>();
Expand All @@ -60,6 +83,12 @@ public DocumentNode Parse()
return new DocumentNode(location, definitions);
}

internal IReadOnlyList<ArgumentNode> ParseArguments()
{
MoveNext();
return ParseArguments(false);
}

private IDefinitionNode ParseDefinition()
{
_description = null;
Expand Down
2 changes: 1 addition & 1 deletion src/Core/Subscriptions.InMemory/InMemoryEventRegistry.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
Expand Down
3 changes: 3 additions & 0 deletions src/Core/Subscriptions.Redis/InternalsVisibleTo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("HotChocolate.Subscriptions.Tests")]
43 changes: 43 additions & 0 deletions src/Core/Subscriptions.Redis/RedisEventRegistry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System.Threading.Tasks;
using StackExchange.Redis;

namespace HotChocolate.Subscriptions.Redis
{
public class RedisEventRegistry
: IEventRegistry
, IEventSender
{
private readonly IConnectionMultiplexer _connection;
private readonly IPayloadSerializer _serializer;

public RedisEventRegistry(
IConnectionMultiplexer connection,
IPayloadSerializer serializer)
{
_connection = connection;
_serializer = serializer;
}

public async Task<IEventStream> SubscribeAsync(
IEventDescription eventDescription)
{
ISubscriber subscriber = _connection.GetSubscriber();

ChannelMessageQueue channel = await subscriber
.SubscribeAsync(eventDescription.ToString());

return new RedisEventStream(eventDescription, channel, _serializer);
}

public async Task SendAsync(IEventMessage message)
{
ISubscriber subscriber = _connection
.GetSubscriber();

string channel = message.Event.ToString();
byte[] payload = _serializer.Serialize(message.Payload);

await subscriber.PublishAsync(channel, payload);
}
}
}
68 changes: 68 additions & 0 deletions src/Core/Subscriptions.Redis/RedisEventStream.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using StackExchange.Redis;

namespace HotChocolate.Subscriptions.Redis
{
public class RedisEventStream : IEventStream
{
private readonly IEventDescription _eventDescription;
private readonly ChannelMessageQueue _channel;
private readonly IPayloadSerializer _serializer;
private bool _isCompleted;

public RedisEventStream(
IEventDescription eventDescription,
ChannelMessageQueue channel,
IPayloadSerializer serializer)
{
_eventDescription = eventDescription;
_channel = channel;
_serializer = serializer;
}

public bool IsCompleted => _isCompleted;

public Task<IEventMessage> ReadAsync() =>
ReadAsync(CancellationToken.None);

public async Task<IEventMessage> ReadAsync(
CancellationToken cancellationToken)
{
ChannelMessage message = await _channel
.ReadAsync(cancellationToken);

var payload = _serializer.Deserialize(message.Message);

return new EventMessage(message.Channel, payload);
}

public async Task CompleteAsync()
{
if (!_isCompleted)
{
await _channel.UnsubscribeAsync();
_isCompleted = true;
}
}

public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}

public void Dispose(bool disposing)
{
if (!_isCompleted)
{
if (disposing)
{
_channel.Unsubscribe();
}
_isCompleted = true;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;
using HotChocolate.Subscriptions.Redis;
using Microsoft.Extensions.DependencyInjection;
using StackExchange.Redis;

namespace HotChocolate.Subscriptions
{
public static class RedisSubscriptionServiceCollectionExtensions
{
public static void AddRedisSubscriptionProvider(
this IServiceCollection services,
ConfigurationOptions options) =>
services
.AddRedisSubscriptionProvider<JsonPayloadSerializer>(options);

public static void AddRedisSubscriptionProvider<TSerializer>(
this IServiceCollection services,
ConfigurationOptions options)
where TSerializer : class, IPayloadSerializer
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}

services
.AddSingleton<IPayloadSerializer, TSerializer>()
.AddSingleton<IConnectionMultiplexer>(sp =>
ConnectionMultiplexer.Connect(options))
.AddSingleton<RedisEventRegistry>()
.AddSingleton<IEventRegistry>(sp =>
sp.GetRequiredService<RedisEventRegistry>())
.AddSingleton<IEventSender>(sp =>
sp.GetRequiredService<RedisEventRegistry>());
}
}
}
32 changes: 32 additions & 0 deletions src/Core/Subscriptions.Redis/Subscriptions.Redis.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">netstandard2.0</TargetFrameworks>
<TargetFrameworks Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">netstandard2.0; net461</TargetFrameworks>
<AssemblyName>HotChocolate.Subscriptions.Redis</AssemblyName>
<RootNamespace>HotChocolate.Subscriptions.Redis</RootNamespace>
<PackageId>HotChocolate.Subscriptions.Redis</PackageId>
<Description>Contains a Redis implementation for a Hot Chocolate GraphQL subscription provider.</Description>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugType>portable</DebugType>
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DebugType>pdbonly</DebugType>
<DebugSymbols>true</DebugSymbols>
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.1.0" />
<PackageReference Include="StackExchange.Redis" Version="2.0.601" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Subscriptions\Subscriptions.csproj" />
</ItemGroup>

</Project>
35 changes: 34 additions & 1 deletion src/Core/Subscriptions.Tests/EventDescriptionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ public void EventDescription_ToString()
string result_b = b.ToString();

// assert
Assert.Equal("event(foo = \"bar\")", result_a);
Assert.Equal("event(foo: \"bar\")", result_a);
Assert.Equal("event", result_b);
}

Expand All @@ -129,5 +129,38 @@ public void EventDescription_GetHashCode()
Assert.Equal(result_a, result_b);
Assert.NotEqual(result_a, result_c);
}

[Fact]
public void EventDescription_Parse_WithArguments()
{
// arrange
string s = "event(foo: \"bar\")";

// act
EventDescription description = EventDescription.Parse(s);

// assert
Assert.Equal("event", description.Name);
Assert.Collection(description.Arguments,
t =>
{
Assert.Equal("foo", t.Name.Value);
Assert.Equal("bar", t.Value.Value);
});
}

[Fact]
public void EventDescription_Parse_WithoutArguments()
{
// arrange
string s = "event";

// act
EventDescription description = EventDescription.Parse(s);

// assert
Assert.Equal("event", description.Name);
Assert.Empty(description.Arguments);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace HotChocolate.Subscriptions.InMemory
public class InMemoryEventRegistryTests
{
[Fact]
public async Task Subscribe_Send_MessageReveived()
public async Task Subscribe_Send_MessageReceived()
{
// arrange
var eventDescription = new EventDescription("foo");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Threading.Tasks;
using System.Threading.Tasks;
using Xunit;

namespace HotChocolate.Subscriptions.InMemory
Expand Down
38 changes: 38 additions & 0 deletions src/Core/Subscriptions.Tests/JsonPayloadSerializerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System.Threading.Tasks;
using Xunit;

namespace HotChocolate.Subscriptions
{
public class JsonPayloadSerializerTests
{
[Fact]
public void GivenSerializer_WhenSerialize_ContentIsValid()
{
// arrange
var serializer = new JsonPayloadSerializer();
var payload = "Foo";

// act
var encoded = serializer.Serialize(payload);

// assert
var expected = new byte[] { 34, 70, 111, 111, 34 };
Assert.Equal(expected, encoded);
}

[Fact]
public void GivenSerializer_WhenDeserialize_ContentIsValid()
{
// arrange
var serializer = new JsonPayloadSerializer();
var content = new byte[] { 34, 70, 111, 111, 34 };

// act
var decoded = serializer.Deserialize(content);

// assert
var expected = "Foo";
Assert.Equal(expected, decoded);
}
}
}
Loading

0 comments on commit 0cf04dd

Please sign in to comment.