diff --git a/.circleci/config.yml b/.circleci/config.yml
index 7e3c5054..8594df23 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -47,15 +47,15 @@ jobs:
- run: dotnet build --configuration Release
- run: dotnet pack --configuration Release
- store_artifacts:
- path: ./src/GraphQL.Common/bin/Release/GraphQL.Common.2.0.0-alpha.3.nupkg
+ path: ./src/GraphQL.Common/bin/Release/GraphQL.Common.2.0.0-alpha.4.nupkg
- store_artifacts:
- path: ./src/GraphQL.Client/bin/Release/GraphQL.Client.2.0.0-alpha.3.nupkg
+ path: ./src/GraphQL.Client/bin/Release/GraphQL.Client.2.0.0-alpha.4.nupkg
- deploy:
name: MyGet
command: |
if [ "${CIRCLE_BRANCH}" == "master" ]; then
- dotnet nuget push ./src/GraphQL.Common/bin/Release/GraphQL.Common.2.0.0-alpha.3.nupkg --api-key $MY_GET_API_KEY --source $MY_GET_SOURCE
- dotnet nuget push ./src/GraphQL.Client/bin/Release/GraphQL.Client.2.0.0-alpha.3.nupkg --api-key $MY_GET_API_KEY --source $MY_GET_SOURCE
+ dotnet nuget push ./src/GraphQL.Common/bin/Release/GraphQL.Common.2.0.0-alpha.4.nupkg --api-key $MY_GET_API_KEY --source $MY_GET_SOURCE
+ dotnet nuget push ./src/GraphQL.Client/bin/Release/GraphQL.Client.2.0.0-alpha.4.nupkg --api-key $MY_GET_API_KEY --source $MY_GET_SOURCE
fi
workflows:
version: 2
diff --git a/GraphQL.Client.sln b/GraphQL.Client.sln
index aa9d13e0..4d28e424 100644
--- a/GraphQL.Client.sln
+++ b/GraphQL.Client.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.27130.2010
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.28407.52
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{47C98B55-08F1-4428-863E-2C5C876DEEFE}"
ProjectSection(SolutionItems) = preProject
@@ -45,6 +45,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{9413
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphQL.Client.Sample", "samples\GraphQL.Client.Sample\GraphQL.Client.Sample.csproj", "{B21E97C3-F328-473F-A054-A4BF272B63F0}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{A2E950A3-BD50-40C0-8189-57B455FFBC62}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GraphQL.Common.Benchmark", "benchmarks\GraphQL.Common.Benchmark\GraphQL.Common.Benchmark.csproj", "{6A935C7C-7DD1-489D-9259-56C1A398D643}"
+EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphQL.Integration.Tests", "tests\GraphQL.Integration.Tests\GraphQL.Integration.Tests.csproj", "{86BC3878-6549-4EF1-9672-B7C15A3FDF46}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IntegrationTestServer", "tests\IntegrationTestServer\IntegrationTestServer.csproj", "{618653E5-41C2-4F17-BE4F-F904267500D4}"
@@ -89,6 +93,10 @@ Global
{D588BC10-6B17-477F-9546-F8D1CE02EACB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D588BC10-6B17-477F-9546-F8D1CE02EACB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D588BC10-6B17-477F-9546-F8D1CE02EACB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6A935C7C-7DD1-489D-9259-56C1A398D643}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6A935C7C-7DD1-489D-9259-56C1A398D643}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6A935C7C-7DD1-489D-9259-56C1A398D643}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6A935C7C-7DD1-489D-9259-56C1A398D643}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -104,6 +112,7 @@ Global
{86BC3878-6549-4EF1-9672-B7C15A3FDF46} = {0B0EDB0F-FF67-4B78-A8DB-B5C23E1FEE8C}
{618653E5-41C2-4F17-BE4F-F904267500D4} = {0B0EDB0F-FF67-4B78-A8DB-B5C23E1FEE8C}
{D588BC10-6B17-477F-9546-F8D1CE02EACB} = {0B0EDB0F-FF67-4B78-A8DB-B5C23E1FEE8C}
+ {6A935C7C-7DD1-489D-9259-56C1A398D643} = {A2E950A3-BD50-40C0-8189-57B455FFBC62}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {387AC1AC-F90C-4EF8-955A-04D495C75AF4}
diff --git a/README.md b/README.md
index f7bfc120..68c7b303 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@ A GraphQL Client for .NET Standard over HTTP.
## Specification:
The Library will try to follow the following standards and documents:
-[GraphQL Specification](http://facebook.github.io/graphql/October2016/)
+[GraphQL Specification](https://facebook.github.io/graphql/June2018/)
[GraphQL HomePage](http://graphql.org/learn/)
## Usage:
@@ -15,32 +15,30 @@ The Library will try to follow the following standards and documents:
### Create a GraphQLRequest:
#### Simple Request:
```csharp
-var heroRequest = new GraphQLRequest {
- Query = @"
+var heroRequest = new GraphQLRequest(@"
{
- hero {
- name
- }
+ hero {
+ name
+ }
}"
-};
+);
```
#### OperationName and Variables Request:
```csharp
-var heroAndFriendsRequest = new GraphQLRequest {
- Query = @"
- query HeroNameAndFriends($episode: Episode) {
- hero(episode: $episode) {
- name
- friends {
- name
- }
- }
- }",
- OperationName = "HeroNameAndFriends",
- Variables = new {
- episode = "JEDI"
- }
+var heroAndFriendsRequest = new GraphQLRequest(@"
+ query HeroNameAndFriends($episode: Episode) {
+ hero(episode: $episode) {
+ name
+ friends {
+ name
+ }
+ }
+ }"){
+ OperationName = "HeroNameAndFriends",
+ Variables = new {
+ episode = "JEDI"
+ }
};
```
diff --git a/SubscriptionIntegrationTest.ConsoleClient/Program.cs b/SubscriptionIntegrationTest.ConsoleClient/Program.cs
index a891236a..95000512 100644
--- a/SubscriptionIntegrationTest.ConsoleClient/Program.cs
+++ b/SubscriptionIntegrationTest.ConsoleClient/Program.cs
@@ -1,4 +1,8 @@
using System;
+using System.Net.WebSockets;
+using System.Reactive.Disposables;
+using System.Reactive.Linq;
+using System.Threading.Tasks;
using GraphQL.Client.Http;
using GraphQL.Common.Request;
@@ -6,39 +10,126 @@ namespace SubsccriptionIntegrationTest.ConsoleClient
{
class Program
{
- static void Main(string[] args)
+ static async Task Main(string[] args)
{
Console.WriteLine("configuring client ...");
- using (var client = new GraphQLHttpClient("http://localhost:5000/graphql/"))
+ using (var client = new GraphQLHttpClient("http://localhost:5000/graphql/", new GraphQLHttpClientOptions{ UseWebSocketForQueriesAndMutations = true }))
{
-#pragma warning disable 618
- var stream = client.CreateSubscriptionStream(new GraphQLRequest
- {
- Query = @"
- subscription {
- messageAdded{
- content
- from {
- displayName
- }
- }
- }"
- },
- e => Console.WriteLine($"WebSocketException: {e.Message} (WebSocketError {e.WebSocketErrorCode}, ErrorCode {e.ErrorCode}, NativeErrorCode {e.NativeErrorCode}"));
-#pragma warning restore 618
Console.WriteLine("subscribing to message stream ...");
- using (var subscription = stream.Subscribe(
- response => Console.WriteLine($"new message from \"{response.Data.messageAdded.from.displayName.Value}\": {response.Data.messageAdded.content.Value}"),
- exception => Console.WriteLine($"message subscription stream failed: {exception}"),
- () => Console.WriteLine($"message subscription stream completed")))
+
+ var subscriptions = new CompositeDisposable();
+
+ subscriptions.Add(client.WebSocketReceiveErrors.Subscribe(e => {
+ if(e is WebSocketException we)
+ Console.WriteLine($"WebSocketException: {we.Message} (WebSocketError {we.WebSocketErrorCode}, ErrorCode {we.ErrorCode}, NativeErrorCode {we.NativeErrorCode}");
+ else
+ Console.WriteLine($"Exception in websocket receive stream: {e.ToString()}");
+ }));
+
+ subscriptions.Add(CreateSubscription("1", client));
+ await Task.Delay(200);
+ subscriptions.Add(CreateSubscription2("2", client));
+ await Task.Delay(200);
+ subscriptions.Add(CreateSubscription("3", client));
+ await Task.Delay(200);
+ subscriptions.Add(CreateSubscription("4", client));
+ await Task.Delay(200);
+ subscriptions.Add(CreateSubscription("5", client));
+ await Task.Delay(200);
+ subscriptions.Add(CreateSubscription("6", client));
+ await Task.Delay(200);
+ subscriptions.Add(CreateSubscription("7", client));
+
+ using (subscriptions)
{
Console.WriteLine("client setup complete");
- Console.WriteLine("press any key to exit");
- Console.Read();
+ var quit = false;
+ do
+ {
+ Console.WriteLine("write message and press enter...");
+ var message = Console.ReadLine();
+ var graphQLRequest = new GraphQLRequest(@"
+ mutation($input: MessageInputType){
+ addMessage(message: $input){
+ content
+ }
+ }")
+ {
+ Variables = new
+ {
+ input = new
+ {
+ fromId = "2",
+ content = message,
+ sentAt = DateTime.Now
+ }
+ }
+ };
+ var result = await client.SendMutationAsync(graphQLRequest).ConfigureAwait(false);
+
+ if(result.Errors != null && result.Errors.Length > 0)
+ {
+ Console.WriteLine($"request returned {result.Errors.Length} errors:");
+ foreach (var item in result.Errors)
+ {
+ Console.WriteLine($"{item.Message}");
+ }
+ }
+ }
+ while(!quit);
Console.WriteLine("shutting down ...");
}
+ Console.WriteLine("subscriptions disposed ...");
}
+ Console.WriteLine("client disposed ...");
+ }
+
+ private static IDisposable CreateSubscription(string id, GraphQLHttpClient client)
+ {
+#pragma warning disable 618
+ var stream = client.CreateSubscriptionStream(new GraphQLRequest(@"
+ subscription {
+ messageAdded{
+ content
+ from {
+ displayName
+ }
+ }
+ }"
+ )
+ { Variables = new { id } });
+#pragma warning restore 618
+
+ return stream.Subscribe(
+ response => Console.WriteLine($"{id}: new message from \"{response.Data.messageAdded.from.displayName.Value}\": {response.Data.messageAdded.content.Value}"),
+ exception => Console.WriteLine($"{id}: message subscription stream failed: {exception}"),
+ () => Console.WriteLine($"{id}: message subscription stream completed"));
+
+ }
+
+
+ private static IDisposable CreateSubscription2(string id, GraphQLHttpClient client)
+ {
+#pragma warning disable 618
+ var stream = client.CreateSubscriptionStream(new GraphQLRequest(@"
+ subscription {
+ contentAdded{
+ content
+ from {
+ displayName
+ }
+ }
+ }"
+ )
+ { Variables = new { id } });
+#pragma warning restore 618
+
+ return stream.Subscribe(
+ response => Console.WriteLine($"{id}: new content from \"{response.Data.contentAdded.from.displayName.Value}\": {response.Data.contentAdded.content.Value}"),
+ exception => Console.WriteLine($"{id}: content subscription stream failed: {exception}"),
+ () => Console.WriteLine($"{id}: content subscription stream completed"));
+
}
}
}
diff --git a/SubscriptionIntegrationTest.ConsoleClient/SubscriptionIntegrationTest.ConsoleClient.csproj b/SubscriptionIntegrationTest.ConsoleClient/SubscriptionIntegrationTest.ConsoleClient.csproj
index 21901c2f..83d4884b 100644
--- a/SubscriptionIntegrationTest.ConsoleClient/SubscriptionIntegrationTest.ConsoleClient.csproj
+++ b/SubscriptionIntegrationTest.ConsoleClient/SubscriptionIntegrationTest.ConsoleClient.csproj
@@ -2,9 +2,14 @@
Exe
- netcoreapp2.1
+ netcoreapp3.0;net461
+ 8.0
+
+
+
+
diff --git a/benchmarks/GraphQL.Common.Benchmark/GraphQL.Common.Benchmark.csproj b/benchmarks/GraphQL.Common.Benchmark/GraphQL.Common.Benchmark.csproj
new file mode 100644
index 00000000..d5ccaa33
--- /dev/null
+++ b/benchmarks/GraphQL.Common.Benchmark/GraphQL.Common.Benchmark.csproj
@@ -0,0 +1,16 @@
+
+
+
+ Exe
+ netcoreapp2.2
+
+
+
+
+
+
+
+
+
+
+
diff --git a/benchmarks/GraphQL.Common.Benchmark/Program.cs b/benchmarks/GraphQL.Common.Benchmark/Program.cs
new file mode 100644
index 00000000..cdbcfce2
--- /dev/null
+++ b/benchmarks/GraphQL.Common.Benchmark/Program.cs
@@ -0,0 +1,36 @@
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Running;
+using System;
+using System.Security.Cryptography;
+
+namespace GraphQL.Common.Benchmark{
+
+ public class Md5VsSha256{
+
+ private const int N = 10000;
+ private readonly byte[] data;
+
+ private readonly SHA256 sha256 = SHA256.Create();
+ private readonly MD5 md5 = MD5.Create();
+
+ public Md5VsSha256(){
+ this.data = new byte[N];
+ new Random(42).NextBytes(this.data);
+ }
+
+ [Benchmark]
+ public byte[] Sha256() => this.sha256.ComputeHash(this.data);
+
+ [Benchmark]
+ public byte[] Md5() => this.md5.ComputeHash(this.data);
+ }
+
+ public class Program{
+
+ public static void Main(string[] args){
+ var summary = BenchmarkRunner.Run();
+ }
+
+ }
+
+}
diff --git a/benchmarks/GraphQL.Common.Benchmark/Request/GraphQLRequestBenchmark.cs b/benchmarks/GraphQL.Common.Benchmark/Request/GraphQLRequestBenchmark.cs
new file mode 100644
index 00000000..c6869149
--- /dev/null
+++ b/benchmarks/GraphQL.Common.Benchmark/Request/GraphQLRequestBenchmark.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Text;
+using BenchmarkDotNet.Attributes;
+using GraphQL.Common.Request;
+
+namespace GraphQL.Common.Benchmark.Request {
+
+ public class GraphQLRequestBenchmark {
+
+ private readonly string query;
+ private readonly string operationName;
+ private readonly dynamic variables;
+ private readonly GraphQLRequest graphQLRequest1 = Generate();
+ private readonly GraphQLRequest graphQLRequest2 = Generate();
+
+ public GraphQLRequestBenchmark() {
+ var random = new Random();
+ var data = new byte[1000];
+ random.NextBytes(data);
+ this.query = Encoding.Default.GetString(data);
+ random.NextBytes(data);
+ this.operationName = Encoding.Default.GetString(data);
+ this.variables = new {
+ objectA = random.NextDouble(),
+ objectB = random.Next()
+ };
+ }
+
+ [Benchmark]
+ public GraphQLRequest Constructor() => new GraphQLRequest(this.query) { OperationName = this.operationName, Variables = this.variables };
+
+ [Benchmark]
+ public bool Equality() => this.graphQLRequest1.Equals(this.graphQLRequest2);
+
+ [Benchmark]
+ public bool InEquality() => !this.graphQLRequest1.Equals(this.graphQLRequest2);
+
+ private static GraphQLRequest Generate() {
+ var random = new Random();
+ var data = new byte[1000];
+ random.NextBytes(data);
+ var query = Encoding.Default.GetString(data);
+ random.NextBytes(data);
+ var operationName = Encoding.Default.GetString(data);
+ var variables = new {
+ objectA = random.NextDouble(),
+ objectB = random.Next()
+ };
+ return new GraphQLRequest(query) {
+ OperationName = operationName,
+ Variables = variables
+ };
+ }
+
+ }
+
+}
diff --git a/root.props b/root.props
index d00301d1..b32dba6a 100644
--- a/root.props
+++ b/root.props
@@ -6,9 +6,9 @@
A GraphQL Client for .NET Standard
True
True
- latest
+ 8.0
en-US
- CS0618;CS1591;CS1701
+ CS0618;CS1591;CS1701;NU5105;NU5125
https://raw.githubusercontent.com/graphql-dotnet/graphql-client/master/assets/logo.64x64.png
https://raw.githubusercontent.com/graphql-dotnet/graphql-client/master/LICENSE.txt
https://github.com/graphql-dotnet/graphql-client
@@ -17,7 +17,7 @@
git
https://github.com/graphql-dotnet/graphql-client.git
True
- 2.0.0-alpha.subscriptions-api.10
+ 2.0.0-alpha.4.subscription-api.7
4
diff --git a/src/GraphQL.Client/GraphQL.Client.csproj b/src/GraphQL.Client/GraphQL.Client.csproj
index 97a78f0e..8043bf35 100644
--- a/src/GraphQL.Client/GraphQL.Client.csproj
+++ b/src/GraphQL.Client/GraphQL.Client.csproj
@@ -14,6 +14,7 @@
+
diff --git a/src/GraphQL.Client/Http/GraphQLHttpClient.cs b/src/GraphQL.Client/Http/GraphQLHttpClient.cs
index 90209d40..e6a76359 100644
--- a/src/GraphQL.Client/Http/GraphQLHttpClient.cs
+++ b/src/GraphQL.Client/Http/GraphQLHttpClient.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.WebSockets;
@@ -38,6 +39,10 @@ public GraphQLHttpClientOptions Options {
set => this.graphQLHttpHandler.Options = value;
}
+ ///
+ [Obsolete("EXPERIMENTAL")]
+ public IObservable WebSocketReceiveErrors => graphQlHttpWebSocket.ReceiveErrors;
+
#endregion
internal readonly GraphQLHttpHandler graphQLHttpHandler;
@@ -69,13 +74,14 @@ public GraphQLHttpClient(string endPoint, GraphQLHttpClientOptions options) : th
/// The EndPoint to be used
/// The Options to be used
public GraphQLHttpClient(Uri endPoint, GraphQLHttpClientOptions options) {
+
+ options.EndPoint = endPoint ?? throw new ArgumentNullException(nameof(endPoint));
+
if (options == null) { throw new ArgumentNullException(nameof(options)); }
- if (options.EndPoint == null) { throw new ArgumentNullException(nameof(options.EndPoint)); }
if (options.JsonSerializerSettings == null) { throw new ArgumentNullException(nameof(options.JsonSerializerSettings)); }
if (options.HttpMessageHandler == null) { throw new ArgumentNullException(nameof(options.HttpMessageHandler)); }
if (options.MediaType == null) { throw new ArgumentNullException(nameof(options.MediaType)); }
- options.EndPoint = endPoint ?? throw new ArgumentNullException(nameof(endPoint));
this.graphQLHttpHandler = new GraphQLHttpHandler(options);
this.graphQlHttpWebSocket = new GraphQLHttpWebSocket(_getWebSocketUri(), options);
}
@@ -107,20 +113,28 @@ internal GraphQLHttpClient(GraphQLHttpClientOptions options, HttpClient httpClie
}
public Task SendQueryAsync(string query, CancellationToken cancellationToken = default) =>
- this.SendQueryAsync(new GraphQLRequest { Query = query }, cancellationToken);
+ this.SendQueryAsync(new GraphQLRequest(query), cancellationToken);
- public Task SendQueryAsync(GraphQLRequest request, CancellationToken cancellationToken = default) =>
- this.graphQLHttpHandler.PostAsync(request, cancellationToken);
+ public Task SendQueryAsync(GraphQLRequest request, CancellationToken cancellationToken = default)
+ {
+ return Options.UseWebSocketForQueriesAndMutations
+ ? this.graphQlHttpWebSocket.Request(request, cancellationToken)
+ : this.graphQLHttpHandler.PostAsync(request, cancellationToken);
+ }
public Task SendMutationAsync(string query, CancellationToken cancellationToken = default) =>
- this.SendMutationAsync(new GraphQLRequest { Query = query }, cancellationToken);
+ this.SendMutationAsync(new GraphQLRequest(query), cancellationToken);
- public Task SendMutationAsync(GraphQLRequest request, CancellationToken cancellationToken = default) =>
- this.graphQLHttpHandler.PostAsync(request, cancellationToken);
+ public Task SendMutationAsync(GraphQLRequest request, CancellationToken cancellationToken = default)
+ {
+ return Options.UseWebSocketForQueriesAndMutations
+ ? this.graphQlHttpWebSocket.Request(request, cancellationToken)
+ : this.graphQLHttpHandler.PostAsync(request, cancellationToken);
+ }
[Obsolete("EXPERIMENTAL API")]
public Task SendSubscribeAsync(string query, CancellationToken cancellationToken = default) =>
- this.SendSubscribeAsync(new GraphQLRequest { Query = query }, cancellationToken);
+ this.SendSubscribeAsync(new GraphQLRequest(query), cancellationToken);
[Obsolete("EXPERIMENTAL API")]
public Task SendSubscribeAsync(GraphQLRequest request, CancellationToken cancellationToken = default)
@@ -152,14 +166,22 @@ public IObservable CreateSubscriptionStream(GraphQLRequest requ
if (_disposed)
throw new ObjectDisposedException(nameof(GraphQLHttpClient));
- return GraphQLHttpSubscriptionHelpers.CreateSubscriptionStream(request, graphQlHttpWebSocket,
- Options, cancellationToken: _cancellationTokenSource.Token);
+ if (subscriptionStreams.ContainsKey(request))
+ return subscriptionStreams[request];
+
+ var observable = graphQlHttpWebSocket.CreateSubscriptionStream(request, Options, cancellationToken: _cancellationTokenSource.Token);
+
+ subscriptionStreams.Add(request, observable);
+ return observable;
}
///
[Obsolete("EXPERIMENTAL API")]
public IObservable CreateSubscriptionStream(GraphQLRequest request, Action webSocketExceptionHandler)
{
+ if (_disposed)
+ throw new ObjectDisposedException(nameof(GraphQLHttpClient));
+
return CreateSubscriptionStream(request, e =>
{
if (e is WebSocketException webSocketException)
@@ -173,10 +195,19 @@ public IObservable CreateSubscriptionStream(GraphQLRequest requ
[Obsolete("EXPERIMENTAL API")]
public IObservable CreateSubscriptionStream(GraphQLRequest request, Action exceptionHandler)
{
- var observable = GraphQLHttpSubscriptionHelpers.CreateSubscriptionStream(request, graphQlHttpWebSocket, Options, exceptionHandler, _cancellationTokenSource.Token);
+ if (_disposed)
+ throw new ObjectDisposedException(nameof(GraphQLHttpClient));
+
+ if(subscriptionStreams.ContainsKey(request))
+ return subscriptionStreams[request];
+
+ var observable = graphQlHttpWebSocket.CreateSubscriptionStream(request, Options, exceptionHandler, _cancellationTokenSource.Token);
+ subscriptionStreams.Add(request, observable);
return observable;
}
+ private Dictionary> subscriptionStreams = new Dictionary>();
+
///
/// Releases unmanaged resources
///
diff --git a/src/GraphQL.Client/Http/GraphQLHttpClientOptions.cs b/src/GraphQL.Client/Http/GraphQLHttpClientOptions.cs
index 38008421..ddce13c7 100644
--- a/src/GraphQL.Client/Http/GraphQLHttpClientOptions.cs
+++ b/src/GraphQL.Client/Http/GraphQLHttpClientOptions.cs
@@ -49,6 +49,11 @@ public class GraphQLHttpClientOptions {
var rnd = new Random();
return TimeSpan.FromSeconds(Math.Min(n, 5) * 1.5 + rnd.NextDouble());
};
+
+ ///
+ /// If , the websocket connection is also used for regular queries and mutations
+ ///
+ public bool UseWebSocketForQueriesAndMutations { get; set; } = false;
}
}
diff --git a/src/GraphQL.Client/Http/GraphQLHttpSubscriptionHelpers.cs b/src/GraphQL.Client/Http/GraphQLHttpSubscriptionHelpers.cs
index 512dc443..ef71a0ce 100644
--- a/src/GraphQL.Client/Http/GraphQLHttpSubscriptionHelpers.cs
+++ b/src/GraphQL.Client/Http/GraphQLHttpSubscriptionHelpers.cs
@@ -15,8 +15,8 @@ namespace GraphQL.Client.Http
public static class GraphQLHttpSubscriptionHelpers
{
internal static IObservable CreateSubscriptionStream(
+ this GraphQLHttpWebSocket graphQlHttpWebSocket,
GraphQLRequest request,
- GraphQLHttpWebSocket graphQlHttpWebSocket,
GraphQLHttpClientOptions options,
Action exceptionHandler = null,
CancellationToken cancellationToken = default)
@@ -36,7 +36,9 @@ internal static IObservable CreateSubscriptionStream(
Type = GQLWebSocketMessageType.GQL_STOP
};
var observable = graphQlHttpWebSocket.ResponseStream
- .Where(response => response.Id == startRequest.Id)
+ .Where(response => {
+ return response != null && response.Id == startRequest.Id;
+ })
.SelectMany(response =>
{
switch (response.Type)
@@ -107,11 +109,16 @@ internal static IObservable CreateSubscriptionStream(
{
try
{
- // if the external handler is not set, propagate all exceptions (default subscription behaviour without Retry())
- if (exceptionHandler == null) throw e;
-
- // exceptions thrown by the handler will propagate to OnError()
- exceptionHandler?.Invoke(e);
+ if (exceptionHandler == null) {
+ // if the external handler is not set, propagate all exceptions except WebSocketExceptions
+ // this will ensure that the client tries to re-establish subscriptions on connection loss
+ if (!(e is WebSocketException)) throw e;
+ }
+ else
+ {
+ // exceptions thrown by the handler will propagate to OnError()
+ exceptionHandler?.Invoke(e);
+ }
// throw exception on the observable to be caught by Retry() or complete sequence if cancellation was requested
return cancellationToken.IsCancellationRequested
@@ -140,5 +147,75 @@ internal static IObservable CreateSubscriptionStream(
// transform to hot observable and auto-connect
.Publish().RefCount();
}
+
+ internal static async Task Request(
+ this GraphQLHttpWebSocket graphQlHttpWebSocket,
+ GraphQLRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ return await Observable.Create(async observer =>
+ {
+ var websocketRequest = new GraphQLWebSocketRequest
+ {
+ Id = Guid.NewGuid().ToString("N"),
+ Type = GQLWebSocketMessageType.GQL_START,
+ Payload = request
+ };
+ var observable = graphQlHttpWebSocket.ResponseStream
+ .Where(response => {
+ return response != null && response.Id == websocketRequest.Id;
+ })
+ .SelectMany(response =>
+ {
+ switch (response.Type)
+ {
+ case GQLWebSocketMessageType.GQL_COMPLETE:
+ Debug.WriteLine($"received 'complete' message on request {websocketRequest.Id}");
+ return Observable.Empty();
+ case GQLWebSocketMessageType.GQL_ERROR:
+ Debug.WriteLine($"received 'error' message on request {websocketRequest.Id}");
+ return Observable.Throw(
+ new GraphQLSubscriptionException(response.Payload));
+ default:
+ Debug.WriteLine($"received response for request {websocketRequest.Id}");
+ return Observable.Return(((JObject)response?.Payload)
+ ?.ToObject());
+ }
+ });
+
+ try
+ {
+ // intialize websocket (completes immediately if socket is already open)
+ await graphQlHttpWebSocket.InitializeWebSocket().ConfigureAwait(false);
+ }
+ catch (Exception e)
+ {
+ // subscribe observer to failed observable
+ return Observable.Throw(e).Subscribe(observer);
+ }
+
+ var disposable = new CompositeDisposable(
+ observable.Subscribe(observer)
+ );
+
+ Debug.WriteLine($"submitting request {websocketRequest.Id}");
+ // send request
+ try
+ {
+ await graphQlHttpWebSocket.SendWebSocketRequest(websocketRequest).ConfigureAwait(false);
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e);
+ throw;
+ }
+
+ return disposable;
+ })
+ // complete sequence on OperationCanceledException, this is triggered by the cancellation token
+ .Catch(exception =>
+ Observable.Empty())
+ .FirstOrDefaultAsync();
+ }
}
}
diff --git a/src/GraphQL.Client/Http/GraphQLHttpWebSocket.cs b/src/GraphQL.Client/Http/GraphQLHttpWebSocket.cs
index c5e2d349..90caf737 100644
--- a/src/GraphQL.Client/Http/GraphQLHttpWebSocket.cs
+++ b/src/GraphQL.Client/Http/GraphQLHttpWebSocket.cs
@@ -1,5 +1,6 @@
using System;
using System.Diagnostics;
+using System.IO;
using System.Net.WebSockets;
using System.Reactive.Disposables;
using System.Reactive.Linq;
@@ -18,34 +19,64 @@ internal class GraphQLHttpWebSocket: IDisposable
{
private readonly Uri webSocketUri;
private readonly GraphQLHttpClientOptions _options;
- private readonly byte[] buffer = new byte[1024 * 1024];
- private readonly ArraySegment arraySegment;
+ private readonly ArraySegment buffer;
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
- private Subject _responseSubject = new Subject();
+ private Subject _responseSubject;
+ private Subject _requestSubject = new Subject();
+ private Subject _exceptionSubject = new Subject();
+ private IDisposable _requestSubscription;
public WebSocketState WebSocketState => clientWebSocket?.State ?? WebSocketState.None;
- private ClientWebSocket clientWebSocket = null;
+ private WebSocket clientWebSocket = null;
private int _connectionAttempt = 0;
public GraphQLHttpWebSocket(Uri webSocketUri, GraphQLHttpClientOptions options)
{
this.webSocketUri = webSocketUri;
_options = options;
- arraySegment = new ArraySegment(buffer);
- ResponseStream = _createResponseStream();
+ buffer = new ArraySegment(new byte[8192]);
+ _responseStream = _createResponseStream();
+
+ _requestSubscription = _requestSubject.Select(request => Observable.FromAsync(() => _sendWebSocketRequest(request))).Concat().Subscribe();
}
- public IObservable ResponseStream { get; }
+ public IObservable ReceiveErrors => _exceptionSubject.AsObservable();
+ public IObservable ResponseStream => _responseStream;
+ public IObservable _responseStream;
+ //private IDisposable _responseStreamConnection;
- public async Task SendWebSocketRequest(GraphQLWebSocketRequest request)
+ public Task SendWebSocketRequest(GraphQLWebSocketRequest request)
{
- await InitializeWebSocket().ConfigureAwait(false);
- var webSocketRequestString = JsonConvert.SerializeObject(request);
- var arraySegmentWebSocketRequest = new ArraySegment(Encoding.UTF8.GetBytes(webSocketRequestString));
- await this.clientWebSocket.SendAsync(arraySegmentWebSocketRequest, WebSocketMessageType.Text, true, _cancellationTokenSource.Token).ConfigureAwait(false);
+ _requestSubject.OnNext(request);
+ return request.SendTask();
+ }
+
+ private async Task _sendWebSocketRequest(GraphQLWebSocketRequest request)
+ {
+ try
+ {
+ if (_cancellationTokenSource.Token.IsCancellationRequested)
+ {
+ request.SendCanceled();
+ return;
+ }
+
+ await InitializeWebSocket().ConfigureAwait(false);
+ var webSocketRequestString = JsonConvert.SerializeObject(request);
+ await this.clientWebSocket.SendAsync(
+ new ArraySegment(Encoding.UTF8.GetBytes(webSocketRequestString)),
+ WebSocketMessageType.Text,
+ true,
+ _cancellationTokenSource.Token).ConfigureAwait(false);
+ request.SendCompleted();
+ }
+ catch (Exception e)
+ {
+ request.SendFailed(e);
+ }
}
public Task InitializeWebSocketTask { get; private set; } = Task.CompletedTask;
@@ -80,12 +111,27 @@ public Task InitializeWebSocket()
return Task.CompletedTask;
// else (re-)create websocket and connect
+ //_responseStreamConnection?.Dispose();
clientWebSocket?.Dispose();
- clientWebSocket = new ClientWebSocket();
- this.clientWebSocket.Options.AddSubProtocol("graphql-ws");
+
+ // fix websocket not supported on win 7 using
+ // https://github.com/PingmanTools/System.Net.WebSockets.Client.Managed
+ clientWebSocket = SystemClientWebSocket.CreateClientWebSocket();
+ switch (clientWebSocket)
+ {
+ case ClientWebSocket nativeWebSocket:
+ nativeWebSocket.Options.AddSubProtocol("graphql-ws");
+ break;
+ case System.Net.WebSockets.Managed.ClientWebSocket managedWebSocket:
+ managedWebSocket.Options.AddSubProtocol("graphql-ws");
+ break;
+ default:
+ throw new NotSupportedException($"unknown websocket type {clientWebSocket.GetType().Name}");
+ }
+
return InitializeWebSocketTask = _connectAsync(_cancellationTokenSource.Token);
}
-
+
private IObservable _createResponseStream()
{
return Observable.Create(_createResultStream)
@@ -96,10 +142,27 @@ private IObservable _createResponseStream()
private async Task _createResultStream(IObserver observer, CancellationToken token)
{
- var observable = await _getReceiveResultStream().ConfigureAwait(false);
+ if (_responseSubject == null || _responseSubject.IsDisposed)
+ {
+ _responseSubject = new Subject();
+ var observable = await _getReceiveResultStream().ConfigureAwait(false);
+ observable.Subscribe(_responseSubject);
+
+ _responseSubject.Subscribe(_ => { }, ex =>
+ {
+ _exceptionSubject.OnNext(ex);
+ _responseSubject?.Dispose();
+ _responseSubject = null;
+ },
+ () => {
+ _responseSubject?.Dispose();
+ _responseSubject = null;
+ });
+ }
+
return new CompositeDisposable
(
- observable.Subscribe(observer),
+ _responseSubject.Subscribe(observer),
Disposable.Create(() =>
{
Debug.WriteLine("response stream disposed");
@@ -115,11 +178,20 @@ private async Task> _getReceiveResultStrea
private async Task _connectAsync(CancellationToken token)
{
- await _backOff().ConfigureAwait(false);
- Debug.WriteLine($"opening websocket {clientWebSocket.GetHashCode()}");
- await clientWebSocket.ConnectAsync(webSocketUri, token).ConfigureAwait(false);
- Debug.WriteLine($"connection established on websocket {clientWebSocket.GetHashCode()}");
- _connectionAttempt = 1;
+ try
+ {
+ await _backOff().ConfigureAwait(false);
+ Debug.WriteLine($"opening websocket {clientWebSocket.GetHashCode()}");
+ await clientWebSocket.ConnectAsync(webSocketUri, token).ConfigureAwait(false);
+ Debug.WriteLine($"connection established on websocket {clientWebSocket.GetHashCode()}");
+ //_responseStreamConnection = _responseStream.Connect();
+ _connectionAttempt = 1;
+ }
+ catch (Exception e)
+ {
+ _exceptionSubject.OnNext(e);
+ throw;
+ }
}
@@ -146,23 +218,42 @@ private async Task _receiveResultAsync()
{
try
{
- Debug.WriteLine("receiving websocket data ...");
- _cancellationTokenSource.Token.ThrowIfCancellationRequested();
- var webSocketReceiveResult =
- await clientWebSocket.ReceiveAsync(arraySegment, CancellationToken.None).ConfigureAwait(false);
- _cancellationTokenSource.Token.ThrowIfCancellationRequested();
- var stringResult = Encoding.UTF8.GetString(arraySegment.Array, 0, webSocketReceiveResult.Count);
- return JsonConvert.DeserializeObject(stringResult);
+ Debug.WriteLine($"receiving data on websocket {clientWebSocket.GetHashCode()} ...");
+ WebSocketReceiveResult webSocketReceiveResult = null;
+
+ using (var ms = new MemoryStream())
+ {
+ do
+ {
+ _cancellationTokenSource.Token.ThrowIfCancellationRequested();
+ webSocketReceiveResult = await clientWebSocket.ReceiveAsync(buffer, CancellationToken.None);
+ ms.Write(buffer.Array, buffer.Offset, webSocketReceiveResult.Count);
+ }
+ while (!webSocketReceiveResult.EndOfMessage);
+
+ _cancellationTokenSource.Token.ThrowIfCancellationRequested();
+ ms.Seek(0, SeekOrigin.Begin);
+
+ if (webSocketReceiveResult.MessageType == WebSocketMessageType.Text)
+ {
+ using (var reader = new StreamReader(ms, Encoding.UTF8))
+ {
+ var stringResult = await reader.ReadToEndAsync();
+ Debug.WriteLine($"data received on websocket {clientWebSocket.GetHashCode()}: {stringResult}");
+ return JsonConvert.DeserializeObject(stringResult);
+ }
+ }
+ else
+ {
+ throw new NotSupportedException("binary websocket messages are not supported");
+ }
+ }
}
catch (Exception e)
{
- Console.WriteLine(e);
+ Debug.WriteLine($"exception thrown while receiving websocket data: {e}");
throw;
}
- finally
- {
- Debug.WriteLine("websocket data received");
- }
}
private async Task _closeAsync(CancellationToken cancellationToken = default)
diff --git a/src/GraphQL.Client/IGraphQLClient.cs b/src/GraphQL.Client/IGraphQLClient.cs
index 60af36ab..098d0524 100644
--- a/src/GraphQL.Client/IGraphQLClient.cs
+++ b/src/GraphQL.Client/IGraphQLClient.cs
@@ -72,6 +72,12 @@ public interface IGraphQLClient : IDisposable {
/// an observable stream for the specified subscription
[Obsolete("EXPERIMENTAL")]
IObservable CreateSubscriptionStream(GraphQLRequest request, Action exceptionHandler);
+
+ ///
+ /// Publishes all exceptions which occur inside the websocket receive stream (i.e. for logging purposes)
+ ///
+ [Obsolete("EXPERIMENTAL")]
+ IObservable WebSocketReceiveErrors { get; }
}
}
diff --git a/src/GraphQL.Client/Obsolete.GraphQLClient.Extensions.cs b/src/GraphQL.Client/Obsolete.GraphQLClient.Extensions.cs
index a7b6dbfc..057e3c8a 100644
--- a/src/GraphQL.Client/Obsolete.GraphQLClient.Extensions.cs
+++ b/src/GraphQL.Client/Obsolete.GraphQLClient.Extensions.cs
@@ -99,8 +99,8 @@ fragment TypeRef on __Type {
}
}";
- private static readonly GraphQLRequest IntrospectionGraphQLRequest = new GraphQLRequest {
- Query = IntrospectionQuery.Replace("\t", "").Replace("\n", "").Replace("\r", ""),
+ private static readonly GraphQLRequest IntrospectionGraphQLRequest = new GraphQLRequest(
+ IntrospectionQuery.Replace("\t", "").Replace("\n", "").Replace("\r", "")){
Variables = null
};
diff --git a/src/GraphQL.Client/Obsolete.GraphQLClient.cs b/src/GraphQL.Client/Obsolete.GraphQLClient.cs
index 73f81450..922927bf 100644
--- a/src/GraphQL.Client/Obsolete.GraphQLClient.cs
+++ b/src/GraphQL.Client/Obsolete.GraphQLClient.cs
@@ -53,7 +53,7 @@ public GraphQLClient(GraphQLClientOptions options) : base(options) { }
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
/// The Response
public Task GetQueryAsync(string query, CancellationToken cancellationToken = default) =>
- this.GetAsync(new GraphQLRequest { Query = query }, cancellationToken);
+ this.GetAsync(new GraphQLRequest(query), cancellationToken);
///
/// Send a via GET
@@ -71,7 +71,7 @@ public Task GetAsync(GraphQLRequest request, CancellationToken
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
/// The Response
public Task PostQueryAsync(string query, CancellationToken cancellationToken = default) =>
- this.PostAsync(new GraphQLRequest { Query = query }, cancellationToken);
+ this.PostAsync(new GraphQLRequest(query), cancellationToken);
///
/// Send a via POST
diff --git a/src/GraphQL.Common/GraphQL.Common.csproj b/src/GraphQL.Common/GraphQL.Common.csproj
index ed75f9dd..116ff1b9 100644
--- a/src/GraphQL.Common/GraphQL.Common.csproj
+++ b/src/GraphQL.Common/GraphQL.Common.csproj
@@ -1,4 +1,4 @@
-
+
@@ -8,7 +8,7 @@
- netstandard1.0;netstandard2.0
+ netstandard2.0
diff --git a/src/GraphQL.Common/Request/GraphQLRequest.cs b/src/GraphQL.Common/Request/GraphQLRequest.cs
index 212a3642..d04f1992 100644
--- a/src/GraphQL.Common/Request/GraphQLRequest.cs
+++ b/src/GraphQL.Common/Request/GraphQLRequest.cs
@@ -1,3 +1,4 @@
+#nullable enable
using System;
using System.Collections.Generic;
@@ -7,7 +8,7 @@ namespace GraphQL.Common.Request {
/// Represents a Query that can be fetched to a GraphQL Server.
/// For more information
///
- public class GraphQLRequest : IEquatable {
+ public class GraphQLRequest : IEquatable {
///
/// The Query
@@ -17,50 +18,60 @@ public class GraphQLRequest : IEquatable {
///
/// If the provided contains multiple named operations, this specifies which operation should be executed.
///
- public string OperationName { get; set; }
+ public string? OperationName { get; set; }
///
/// The Variables
///
- public dynamic Variables { get; set; }
+ public dynamic? Variables { get; set; }
+
+ ///
+ /// Initialize a new GraphQLRequest
+ ///
+ /// The Query
+ public GraphQLRequest(string query){
+ this.Query = query;
+ }
+
+ ///
+ /// Initialize a new GraphQLRequest
+ ///
+ [Obsolete("Pass query as parameter! This constructor will be removed in a future version!")]
+ public GraphQLRequest()
+ {
+ this.Query = string.Empty;
+ }
///
- public override bool Equals(object obj) => this.Equals(obj as GraphQLRequest);
+ public override bool Equals(object? obj) => this.Equals(obj as GraphQLRequest);
///
- public bool Equals(GraphQLRequest other) {
- if (other == null) {
- return false;
- }
- if (ReferenceEquals(this, other)) {
- return true;
- }
- if (!Equals(this.Query, other.Query)) {
- return false;
- }
- if (!Equals(this.OperationName, other.OperationName)) {
- return false;
- }
- if (!Equals(this.Variables, other.Variables)) {
- return false;
- }
+ public bool Equals(GraphQLRequest? other) {
+ if (other == null) {return false;}
+ if (ReferenceEquals(this, other)) { return true; }
+ if (!EqualityComparer.Default.Equals(this.Query, other.Query)) { return false; }
+ if (!EqualityComparer.Default.Equals(this.OperationName, other.OperationName)) { return false; }
+ if (!EqualityComparer.Default.Equals(this.Variables, other.Variables)) { return false; }
return true;
}
///
- public override int GetHashCode() {
- var hashCode = -689803966;
- hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(this.Query);
- hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(this.OperationName);
- hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(this.Variables);
- return hashCode;
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ var hashCode = EqualityComparer.Default.GetHashCode(Query);
+ hashCode = (hashCode * 397) ^ EqualityComparer.Default.GetHashCode(OperationName);
+ hashCode = (hashCode * 397) ^ EqualityComparer.Default.GetHashCode(Variables);
+ return hashCode;
+ }
}
///
- public static bool operator ==(GraphQLRequest request1, GraphQLRequest request2) => EqualityComparer.Default.Equals(request1, request2);
+ public static bool operator ==(GraphQLRequest? request1, GraphQLRequest? request2) => EqualityComparer.Default.Equals(request1, request2);
///
- public static bool operator !=(GraphQLRequest request1, GraphQLRequest request2) => !(request1 == request2);
+ public static bool operator !=(GraphQLRequest? request1, GraphQLRequest? request2) => !(request1 == request2);
}
diff --git a/src/GraphQL.Common/Request/GraphQLWebSocketRequest.cs b/src/GraphQL.Common/Request/GraphQLWebSocketRequest.cs
index 46e14dce..d805efc4 100644
--- a/src/GraphQL.Common/Request/GraphQLWebSocketRequest.cs
+++ b/src/GraphQL.Common/Request/GraphQLWebSocketRequest.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Threading.Tasks;
namespace GraphQL.Common.Request {
@@ -24,6 +25,30 @@ public class GraphQLWebSocketRequest : IEquatable {
///
public GraphQLRequest Payload { get; set; }
+ private TaskCompletionSource _tcs = new TaskCompletionSource();
+
+ ///
+ /// Task used to await the actual send operation and to convey potential exceptions
+ ///
+ ///
+ public Task SendTask() => _tcs.Task;
+
+ ///
+ /// gets called when the send operation for this request has completed sucessfully
+ ///
+ public void SendCompleted() => _tcs.SetResult(true);
+
+ ///
+ /// gets called when an exception occurs during the send operation
+ ///
+ ///
+ public void SendFailed(Exception e) => _tcs.SetException(e);
+
+ ///
+ /// gets called when the GraphQLHttpWebSocket has been disposed before the send operation for this request has started
+ ///
+ public void SendCanceled() => _tcs.SetCanceled();
+
///
public override bool Equals(object obj) => this.Equals(obj as GraphQLWebSocketRequest);
diff --git a/src/GraphQL.Common/Response/GraphQLLocation.cs b/src/GraphQL.Common/Response/GraphQLLocation.cs
index 913a7a44..c8eb8c5c 100644
--- a/src/GraphQL.Common/Response/GraphQLLocation.cs
+++ b/src/GraphQL.Common/Response/GraphQLLocation.cs
@@ -1,3 +1,4 @@
+#nullable enable
using System;
using System.Collections.Generic;
@@ -6,7 +7,7 @@ namespace GraphQL.Common.Response {
///
/// Represents the location where the has been found
///
- public class GraphQLLocation : IEquatable {
+ public class GraphQLLocation : IEquatable {
///
/// The Column
@@ -19,38 +20,25 @@ public class GraphQLLocation : IEquatable {
public uint Line { get; set; }
///
- public override bool Equals(object obj) => this.Equals(obj as GraphQLLocation);
+ public override bool Equals(object? obj) => this.Equals(obj as GraphQLLocation);
///
- public bool Equals(GraphQLLocation other) {
- if (other == null) {
- return false;
- }
- if (ReferenceEquals(this, other)) {
- return true;
- }
- if (!Equals(this.Column, other.Column)) {
- return false;
- }
- if (!Equals(this.Line, other.Line)) {
- return false;
- }
+ public bool Equals(GraphQLLocation? other) {
+ if (other == null) { return false; }
+ if (ReferenceEquals(this, other)) { return true; }
+ if (!EqualityComparer.Default.Equals(this.Column, other.Column)) { return false; }
+ if (!EqualityComparer.Default.Equals(this.Line, other.Line)) { return false; }
return true;
}
///
- public override int GetHashCode() {
- var hashCode = 412437926;
- hashCode = hashCode * -1521134295 + this.Column.GetHashCode();
- hashCode = hashCode * -1521134295 + this.Line.GetHashCode();
- return hashCode;
- }
+ public override int GetHashCode() => EqualityComparer.Default.GetHashCode(this);
///
- public static bool operator ==(GraphQLLocation location1, GraphQLLocation location2) => EqualityComparer.Default.Equals(location1, location2);
+ public static bool operator ==(GraphQLLocation? location1, GraphQLLocation? location2) => EqualityComparer.Default.Equals(location1, location2);
///
- public static bool operator !=(GraphQLLocation location1, GraphQLLocation location2) => !(location1 == location2);
+ public static bool operator !=(GraphQLLocation? location1, GraphQLLocation? location2) => !(location1 == location2);
}
diff --git a/tests/GraphQL.Client.Tests/GraphQL.Client.Tests.csproj b/tests/GraphQL.Client.Tests/GraphQL.Client.Tests.csproj
index 660883af..1c9a24d9 100644
--- a/tests/GraphQL.Client.Tests/GraphQL.Client.Tests.csproj
+++ b/tests/GraphQL.Client.Tests/GraphQL.Client.Tests.csproj
@@ -4,16 +4,17 @@
+ true
netcoreapp3.0
-
+
-
+
diff --git a/tests/GraphQL.Client.Tests/GraphQLClientGetTests.cs b/tests/GraphQL.Client.Tests/GraphQLClientGetTests.cs
index faad38e8..2cdc0dfa 100644
--- a/tests/GraphQL.Client.Tests/GraphQLClientGetTests.cs
+++ b/tests/GraphQL.Client.Tests/GraphQLClientGetTests.cs
@@ -8,14 +8,12 @@ public class GraphQLClientGetTests : BaseGraphQLClientTest {
[Fact]
public async void QueryGetAsyncFact() {
- var graphQLRequest = new GraphQLRequest {
- Query = @"
+ var graphQLRequest = new GraphQLRequest(@"
{
person(personID: ""1"") {
name
}
- }"
- };
+ }");
var response = await this.GraphQLClient.GetAsync(graphQLRequest).ConfigureAwait(false);
Assert.Equal("Luke Skywalker", response.Data.person.name.Value);
@@ -24,8 +22,7 @@ public async void QueryGetAsyncFact() {
[Fact]
public async void OperationNameGetAsyncFact() {
- var graphQLRequest = new GraphQLRequest {
- Query = @"
+ var graphQLRequest = new GraphQLRequest(@"
query Person{
person(personID: ""1"") {
name
@@ -36,7 +33,7 @@ query Planet {
planet(planetID: ""1"") {
name
}
- }",
+ }") {
OperationName = "Person"
};
var response = await this.GraphQLClient.GetAsync(graphQLRequest).ConfigureAwait(false);
@@ -47,13 +44,12 @@ query Planet {
[Fact]
public async void VariablesGetAsyncFact() {
- var graphQLRequest = new GraphQLRequest {
- Query = @"
+ var graphQLRequest = new GraphQLRequest (@"
query Person($personId: ID!){
person(personID: $personId) {
name
}
- }",
+ }") {
Variables = new {
personId = "1"
}
@@ -66,8 +62,7 @@ query Person($personId: ID!){
[Fact]
public async void OperationNameVariableGetAsyncFact() {
- var graphQLRequest = new GraphQLRequest {
- Query = @"
+ var graphQLRequest = new GraphQLRequest(@"
query Person($personId: ID!){
person(personID: $personId) {
name
@@ -78,7 +73,7 @@ query Planet {
planet(planetID: ""1"") {
name
}
- }",
+ }"){
OperationName = "Person",
Variables = new {
personId = "1"
diff --git a/tests/GraphQL.Client.Tests/GraphQLClientPostTests.cs b/tests/GraphQL.Client.Tests/GraphQLClientPostTests.cs
index ba56c9a1..aacc1587 100644
--- a/tests/GraphQL.Client.Tests/GraphQLClientPostTests.cs
+++ b/tests/GraphQL.Client.Tests/GraphQLClientPostTests.cs
@@ -9,14 +9,13 @@ public class GraphQLClientPostTests : BaseGraphQLClientTest {
[Fact]
public async void QueryPostAsyncFact() {
- var graphQLRequest = new GraphQLRequest {
- Query = @"
+ var graphQLRequest = new GraphQLRequest(@"
{
person(personID: ""1"") {
name
}
}"
- };
+ );
var response = await this.GraphQLClient.PostAsync(graphQLRequest).ConfigureAwait(false);
Assert.Equal("Luke Skywalker", response.Data.person.name.Value);
@@ -24,17 +23,14 @@ public async void QueryPostAsyncFact() {
}
[Fact]
- public async void QueryPostAsyncWithoutUtf8EncodingFact()
- {
- var graphQLRequest = new GraphQLRequest
- {
- Query = @"
+ public async void QueryPostAsyncWithoutUtf8EncodingFact(){
+ var graphQLRequest = new GraphQLRequest(@"
{
person(personID: ""1"") {
name
}
}"
- };
+ );
this.GraphQLClient.Options.MediaType = MediaTypeHeaderValue.Parse("application/json");
var response = await this.GraphQLClient.PostAsync(graphQLRequest).ConfigureAwait(false);
@@ -44,8 +40,7 @@ public async void QueryPostAsyncWithoutUtf8EncodingFact()
[Fact]
public async void OperationNamePostAsyncFact() {
- var graphQLRequest = new GraphQLRequest {
- Query = @"
+ var graphQLRequest = new GraphQLRequest(@"
query Person{
person(personID: ""1"") {
name
@@ -56,7 +51,7 @@ query Planet {
planet(planetID: ""1"") {
name
}
- }",
+ }") {
OperationName = "Person"
};
var response = await this.GraphQLClient.PostAsync(graphQLRequest).ConfigureAwait(false);
@@ -67,13 +62,12 @@ query Planet {
[Fact]
public async void VariablesPostAsyncFact() {
- var graphQLRequest = new GraphQLRequest {
- Query = @"
+ var graphQLRequest = new GraphQLRequest(@"
query Person($personId: ID!){
person(personID: $personId) {
name
}
- }",
+ }") {
Variables = new {
personId = "1"
}
@@ -86,8 +80,7 @@ query Person($personId: ID!){
[Fact]
public async void OperationNameVariablePostAsyncFact() {
- var graphQLRequest = new GraphQLRequest {
- Query = @"
+ var graphQLRequest = new GraphQLRequest(@"
query Person($personId: ID!){
person(personID: $personId) {
name
@@ -98,7 +91,7 @@ query Planet {
planet(planetID: ""1"") {
name
}
- }",
+ }") {
OperationName = "Person",
Variables = new {
personId = "1"
diff --git a/tests/GraphQL.Client.Tests/Http/GraphQLHttpClientSendQueryAsyncTest.cs b/tests/GraphQL.Client.Tests/Http/GraphQLHttpClientSendQueryAsyncTest.cs
index 82062542..2d21100e 100644
--- a/tests/GraphQL.Client.Tests/Http/GraphQLHttpClientSendQueryAsyncTest.cs
+++ b/tests/GraphQL.Client.Tests/Http/GraphQLHttpClientSendQueryAsyncTest.cs
@@ -1,42 +1,36 @@
-using GraphQL.Client.Http;
-using GraphQL.Common.Request;
using System;
-using System.Collections.Generic;
using System.Net.Http;
-using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using GraphQL.Client.Http;
+using GraphQL.Common.Request;
using Xunit;
namespace GraphQL.Client.Tests.Http
{
- public class GraphQLHttpClientSendQueryAsyncTest
- {
+
+ public class GraphQLHttpClientSendQueryAsyncTest{
// Relates to an issue with the 1.x versions.
// See: https://github.com/graphql-dotnet/graphql-client/issues/53
[Fact]
- public async void SendQueryAsyncShouldPreserveUriParametersFact()
- {
+ public async void SendQueryAsyncShouldPreserveUriParametersFact(){
var endpoint = new Uri("http://localhost/api/graphql?code=my-secret-api-key");
var handlerStub = new HttpHandlerStub();
- GraphQLHttpClientOptions options = new GraphQLHttpClientOptions()
- {
+ var options = new GraphQLHttpClientOptions(){
EndPoint = endpoint,
HttpMessageHandler = handlerStub
};
- GraphQLHttpClient systemUnderTest = new GraphQLHttpClient(options);
+ var systemUnderTest = new GraphQLHttpClient(options);
- var response = await systemUnderTest.SendQueryAsync(new GraphQLRequest
- {
- Query = @"
+ var response = await systemUnderTest.SendQueryAsync(new GraphQLRequest(@"
{
person(personID: ""1"") {
name
}
}"
- });
+ ));
var actualRequestUri = handlerStub.LastRequest.RequestUri;
var queryParams = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(actualRequestUri.Query);
@@ -45,20 +39,21 @@ public async void SendQueryAsyncShouldPreserveUriParametersFact()
}
- private class HttpHandlerStub : HttpMessageHandler
- {
+ private class HttpHandlerStub : HttpMessageHandler{
+
public HttpRequestMessage LastRequest { get; private set; }
public CancellationToken LastCancellationToken { get; private set; }
- protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
- {
- LastRequest = request;
- LastCancellationToken = cancellationToken;
- var response = new HttpResponseMessage(System.Net.HttpStatusCode.OK);
- response.Content = new StringContent("{}");
+ protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken){
+ this.LastRequest = request;
+ this.LastCancellationToken = cancellationToken;
+ var response = new HttpResponseMessage(System.Net.HttpStatusCode.OK){
+ Content = new StringContent("{}")
+ };
return Task.FromResult(response);
}
+
}
}
diff --git a/tests/GraphQL.Client.Tests/Http/HttpClientExtensionsTest.cs b/tests/GraphQL.Client.Tests/Http/HttpClientExtensionsTest.cs
index 1738f0a2..607c7e28 100644
--- a/tests/GraphQL.Client.Tests/Http/HttpClientExtensionsTest.cs
+++ b/tests/GraphQL.Client.Tests/Http/HttpClientExtensionsTest.cs
@@ -13,14 +13,13 @@ public class HttpClientExtensionsTest : BaseGraphQLClientTest {
[Fact]
public async void QueryGetAsyncFact() {
- var graphQLRequest = new GraphQLRequest {
- Query = @"
+ var graphQLRequest = new GraphQLRequest(@"
{
person(personID: ""1"") {
name
}
}"
- };
+ );
var response = await this.GraphQLHttpClient.SendQueryAsync(graphQLRequest).ConfigureAwait(false);
Assert.Equal("Luke Skywalker", response.Data.person.name.Value);
@@ -29,8 +28,7 @@ public async void QueryGetAsyncFact() {
[Fact]
public async void OperationNameGetAsyncFact() {
- var graphQLRequest = new GraphQLRequest {
- Query = @"
+ var graphQLRequest = new GraphQLRequest(@"
query Person{
person(personID: ""1"") {
name
@@ -41,7 +39,7 @@ query Planet {
planet(planetID: ""1"") {
name
}
- }",
+ }"){
OperationName = "Person"
};
var response = await this.GraphQLHttpClient.SendQueryAsync(graphQLRequest).ConfigureAwait(false);
@@ -52,13 +50,12 @@ query Planet {
[Fact]
public async void VariablesGetAsyncFact() {
- var graphQLRequest = new GraphQLRequest {
- Query = @"
+ var graphQLRequest = new GraphQLRequest(@"
query Person($personId: ID!){
person(personID: $personId) {
name
}
- }",
+ }") {
Variables = new {
personId = "1"
}
@@ -71,8 +68,7 @@ query Person($personId: ID!){
[Fact]
public async void OperationNameVariableGetAsyncFact() {
- var graphQLRequest = new GraphQLRequest {
- Query = @"
+ var graphQLRequest = new GraphQLRequest(@"
query Person($personId: ID!){
person(personID: $personId) {
name
@@ -83,7 +79,7 @@ query Planet {
planet(planetID: ""1"") {
name
}
- }",
+ }") {
OperationName = "Person",
Variables = new {
personId = "1"
diff --git a/tests/GraphQL.Client.Tests/IGraphQLClientTest.cs b/tests/GraphQL.Client.Tests/IGraphQLClientTest.cs
index 7b5b6368..7c384d55 100644
--- a/tests/GraphQL.Client.Tests/IGraphQLClientTest.cs
+++ b/tests/GraphQL.Client.Tests/IGraphQLClientTest.cs
@@ -8,14 +8,13 @@ public class IGraphQLClientTest : BaseGraphQLClientTest {
[Fact]
public async void QueryGetAsyncFact() {
- var graphQLRequest = new GraphQLRequest {
- Query = @"
+ var graphQLRequest = new GraphQLRequest(@"
{
person(personID: ""1"") {
name
}
}"
- };
+ );
var response = await this.GraphQLClientSwapi.SendQueryAsync(graphQLRequest).ConfigureAwait(false);
Assert.Equal("Luke Skywalker", response.Data.person.name.Value);
@@ -24,8 +23,7 @@ public async void QueryGetAsyncFact() {
[Fact]
public async void OperationNameGetAsyncFact() {
- var graphQLRequest = new GraphQLRequest {
- Query = @"
+ var graphQLRequest = new GraphQLRequest(@"
query Person{
person(personID: ""1"") {
name
@@ -36,7 +34,7 @@ query Planet {
planet(planetID: ""1"") {
name
}
- }",
+ }") {
OperationName = "Person"
};
var response = await this.GraphQLClientSwapi.SendQueryAsync(graphQLRequest).ConfigureAwait(false);
@@ -47,13 +45,12 @@ query Planet {
[Fact]
public async void VariablesGetAsyncFact() {
- var graphQLRequest = new GraphQLRequest {
- Query = @"
+ var graphQLRequest = new GraphQLRequest(@"
query Person($personId: ID!){
person(personID: $personId) {
name
}
- }",
+ }") {
Variables = new {
personId = "1"
}
@@ -66,8 +63,7 @@ query Person($personId: ID!){
[Fact]
public async void OperationNameVariableGetAsyncFact() {
- var graphQLRequest = new GraphQLRequest {
- Query = @"
+ var graphQLRequest = new GraphQLRequest(@"
query Person($personId: ID!){
person(personID: $personId) {
name
@@ -78,7 +74,7 @@ query Planet {
planet(planetID: ""1"") {
name
}
- }",
+ }") {
OperationName = "Person",
Variables = new {
personId = "1"
diff --git a/tests/GraphQL.Common.Tests/AssertGraphQL.cs b/tests/GraphQL.Common.Tests/AssertGraphQL.cs
index a81f38ad..4115fc9f 100644
--- a/tests/GraphQL.Common.Tests/AssertGraphQL.cs
+++ b/tests/GraphQL.Common.Tests/AssertGraphQL.cs
@@ -6,8 +6,6 @@ namespace GraphQL.Common.Tests {
public static class AssertGraphQL {
- public static void CorrectGraphQLRequest(GraphQLRequest graphQLRequest) => Assert.NotNull(graphQLRequest.Query);
-
public static void CorrectGraphQLResponse(GraphQLResponse graphQLResponse) {
Assert.NotNull(graphQLResponse.Data);
Assert.Null(graphQLResponse.Errors);
diff --git a/tests/GraphQL.Common.Tests/GraphQL.Common.Tests.csproj b/tests/GraphQL.Common.Tests/GraphQL.Common.Tests.csproj
index e0a660a7..4aae0487 100644
--- a/tests/GraphQL.Common.Tests/GraphQL.Common.Tests.csproj
+++ b/tests/GraphQL.Common.Tests/GraphQL.Common.Tests.csproj
@@ -1,9 +1,10 @@
-
+
+ true
netcoreapp3.0
diff --git a/tests/GraphQL.Common.Tests/Request/GraphQLRequestConsts.cs b/tests/GraphQL.Common.Tests/Request/GraphQLRequestConsts.cs
index 9d3fca63..520ca4b6 100644
--- a/tests/GraphQL.Common.Tests/Request/GraphQLRequestConsts.cs
+++ b/tests/GraphQL.Common.Tests/Request/GraphQLRequestConsts.cs
@@ -7,130 +7,122 @@ public static class GraphQLRequestConsts {
///
///
///
- public static GraphQLRequest FieldsRequest1 { get; } = new GraphQLRequest {
- Query = @"
- {
- hero {
- name
- }
- }",
+ public static GraphQLRequest FieldsRequest1 { get; } = new GraphQLRequest(@"
+ {
+ hero {
+ name
+ }
+ }") {
Variables = null
};
///
///
///
- public static GraphQLRequest FieldsRequest2 { get; } = new GraphQLRequest {
- Query = @"
- {
- hero {
+ public static GraphQLRequest FieldsRequest2 { get; } = new GraphQLRequest(@"
+ {
+ hero {
+ name
+ # Queries can have comments!
+ friends {
name
- # Queries can have comments!
- friends {
- name
- }
}
- }",
+ }
+ }") {
Variables = null
};
///
///
///
- public static GraphQLRequest ArgumentsRequest1 { get; } = new GraphQLRequest {
- Query = @"
- {
- human(id: ""1000"") {
- name
- height
- }
- }",
+ public static GraphQLRequest ArgumentsRequest1 { get; } = new GraphQLRequest(@"
+ {
+ human(id: ""1000"") {
+ name
+ height
+ }
+ }") {
Variables = null
};
///
///
///
- public static GraphQLRequest ArgumentsRequest2 { get; } = new GraphQLRequest {
- Query = @"
- {
- human(id: ""1000"") {
- name
- height(unit: FOOT)
- }
- }",
+ public static GraphQLRequest ArgumentsRequest2 { get; } = new GraphQLRequest(@"
+ {
+ human(id: ""1000"") {
+ name
+ height(unit: FOOT)
+ }
+ }") {
Variables = null
};
///
///
///
- public static GraphQLRequest AliasesRequest { get; } = new GraphQLRequest {
- Query = @"
- {
- empireHero: hero(episode: EMPIRE) {
- name
- }
- jediHero: hero(episode: JEDI) {
- name
- }
- }",
+ public static GraphQLRequest AliasesRequest { get; } = new GraphQLRequest(@"
+ {
+ empireHero: hero(episode: EMPIRE) {
+ name
+ }
+ jediHero: hero(episode: JEDI) {
+ name
+ }
+ }") {
Variables = null
};
///
///
///
- public static GraphQLRequest FragmentsRequest { get; } = new GraphQLRequest {
- Query = @"
- {
- leftComparison: hero(episode: EMPIRE) {
- ...comparisonFields
- }
- rightComparison: hero(episode: JEDI) {
- ...comparisonFields
- }
+ public static GraphQLRequest FragmentsRequest { get; } = new GraphQLRequest(@"
+ {
+ leftComparison: hero(episode: EMPIRE) {
+ ...comparisonFields
+ }
+ rightComparison: hero(episode: JEDI) {
+ ...comparisonFields
}
+ }
- fragment comparisonFields on Character {
+ fragment comparisonFields on Character {
+ name
+ appearsIn
+ friends {
name
- appearsIn
- friends {
- name
- }
- }",
+ }
+ }") {
Variables = null
};
///
///
///
- public static GraphQLRequest OperationNameRequest { get; } = new GraphQLRequest {
- Query = @"
- query HeroNameAndFriends {
- hero {
+ public static GraphQLRequest OperationNameRequest { get; } = new GraphQLRequest(@"
+ query HeroNameAndFriends {
+ hero {
+ name
+ friends {
name
- friends {
- name
- }
}
- }",
+ }
+ }") {
Variables = null
};
///
///
///
- public static GraphQLRequest VariablesRequest { get; } = new GraphQLRequest {
- Query = @"
- query HeroNameAndFriends($episode: Episode) {
- hero(episode: $episode) {
+ public static GraphQLRequest VariablesRequest { get; } = new GraphQLRequest(@"
+ query HeroNameAndFriends($episode: Episode) {
+ hero(episode: $episode) {
+ name
+ friends {
name
- friends {
- name
- }
}
- }",
+ }
+ }") {
Variables = new {
episode = "JEDI"
}
@@ -139,16 +131,15 @@ query HeroNameAndFriends($episode: Episode) {
///
///
///
- public static GraphQLRequest DirectivesRequest { get; } = new GraphQLRequest {
- Query = @"
- query Hero($episode: Episode, $withFriends: Boolean!) {
- hero(episode: $episode) {
+ public static GraphQLRequest DirectivesRequest { get; } = new GraphQLRequest(@"
+ query Hero($episode: Episode, $withFriends: Boolean!) {
+ hero(episode: $episode) {
+ name
+ friends @include(if: $withFriends) {
name
- friends @include(if: $withFriends) {
- name
- }
}
- }",
+ }
+ }") {
Variables = new {
episode = "JEDI",
withFriends = false
@@ -158,14 +149,13 @@ friends @include(if: $withFriends) {
///
///
///
- public static GraphQLRequest MutationsRequest { get; } = new GraphQLRequest {
- Query = @"
- mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
- createReview(episode: $ep, review: $review) {
- stars
- commentary
- }
- }",
+ public static GraphQLRequest MutationsRequest { get; } = new GraphQLRequest(@"
+ mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
+ createReview(episode: $ep, review: $review) {
+ stars
+ commentary
+ }
+ }") {
Variables = new {
ep = "JEDI",
review = new {
@@ -178,19 +168,18 @@ mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
///
///
///
- public static GraphQLRequest InlineFragmentsRequest { get; } = new GraphQLRequest {
- Query = @"
- query HeroForEpisode($ep: Episode!) {
- hero(episode: $ep) {
- name
- ... on Droid {
- primaryFunction
- }
- ... on Human {
- height
- }
+ public static GraphQLRequest InlineFragmentsRequest { get; } = new GraphQLRequest(@"
+ query HeroForEpisode($ep: Episode!) {
+ hero(episode: $ep) {
+ name
+ ... on Droid {
+ primaryFunction
}
- }",
+ ... on Human {
+ height
+ }
+ }
+ }") {
Variables = new {
ep = "JEDI"
}
@@ -199,22 +188,21 @@ ... on Human {
///
///
///
- public static GraphQLRequest MetaFieldsRequest { get; } = new GraphQLRequest {
- Query = @"
- {
- search(text: ""an"") {
- __typename
- ...on Human {
- name
- }
- ... on Droid {
- name
- }
- ... on Starship {
- name
- }
+ public static GraphQLRequest MetaFieldsRequest { get; } = new GraphQLRequest(@"
+ {
+ search(text: ""an"") {
+ __typename
+ ...on Human {
+ name
+ }
+ ... on Droid {
+ name
}
- }",
+ ... on Starship {
+ name
+ }
+ }
+ }") {
Variables = null
};
diff --git a/tests/GraphQL.Common.Tests/Request/GraphQLRequestTests.cs b/tests/GraphQL.Common.Tests/Request/GraphQLRequestTests.cs
index 2512fe3b..6eb0febd 100644
--- a/tests/GraphQL.Common.Tests/Request/GraphQLRequestTests.cs
+++ b/tests/GraphQL.Common.Tests/Request/GraphQLRequestTests.cs
@@ -7,49 +7,41 @@ public class GraphQLRequestTests {
[Fact]
public void FieldsRequest1Fact() {
var graphQLRequest = GraphQLRequestConsts.FieldsRequest1;
- AssertGraphQL.CorrectGraphQLRequest(graphQLRequest);
}
[Fact]
public void FieldsRequest2Fact() {
var graphQLRequest = GraphQLRequestConsts.FieldsRequest2;
- AssertGraphQL.CorrectGraphQLRequest(graphQLRequest);
}
[Fact]
public void ArgumentsRequest1Fact() {
var graphQLRequest = GraphQLRequestConsts.ArgumentsRequest1;
- AssertGraphQL.CorrectGraphQLRequest(graphQLRequest);
}
[Fact]
public void ArgumentsRequest2Fact() {
var graphQLRequest = GraphQLRequestConsts.ArgumentsRequest2;
- AssertGraphQL.CorrectGraphQLRequest(graphQLRequest);
}
[Fact]
public void AliasesRequestFact() {
var graphQLRequest = GraphQLRequestConsts.AliasesRequest;
- AssertGraphQL.CorrectGraphQLRequest(graphQLRequest);
}
[Fact]
public void FragmentsRequestFact() {
var graphQLRequest = GraphQLRequestConsts.FragmentsRequest;
- AssertGraphQL.CorrectGraphQLRequest(graphQLRequest);
}
[Fact]
public void OperationNameRequestFact() {
var graphQLRequest = GraphQLRequestConsts.OperationNameRequest;
- AssertGraphQL.CorrectGraphQLRequest(graphQLRequest);
}
[Fact]
public void VariablesRequestFact() {
var graphQLRequest = GraphQLRequestConsts.VariablesRequest;
- AssertGraphQL.CorrectGraphQLRequest(graphQLRequest);
Assert.NotNull(graphQLRequest.Variables);
Assert.Equal("JEDI", graphQLRequest.Variables.episode);
}
@@ -57,7 +49,6 @@ public void VariablesRequestFact() {
[Fact]
public void DirectivesRequestFact() {
var graphQLRequest = GraphQLRequestConsts.DirectivesRequest;
- AssertGraphQL.CorrectGraphQLRequest(graphQLRequest);
Assert.NotNull(graphQLRequest.Variables);
Assert.Equal("JEDI", graphQLRequest.Variables.episode);
Assert.Equal(false, graphQLRequest.Variables.withFriends);
@@ -66,7 +57,6 @@ public void DirectivesRequestFact() {
[Fact]
public void MutationsRequestFact() {
var graphQLRequest = GraphQLRequestConsts.MutationsRequest;
- AssertGraphQL.CorrectGraphQLRequest(graphQLRequest);
Assert.NotNull(graphQLRequest.Variables);
Assert.Equal("JEDI", graphQLRequest.Variables.ep);
Assert.Equal(5, graphQLRequest.Variables.review.stars);
@@ -76,7 +66,6 @@ public void MutationsRequestFact() {
[Fact]
public void InlineFragmentsRequestFact() {
var graphQLRequest = GraphQLRequestConsts.InlineFragmentsRequest;
- AssertGraphQL.CorrectGraphQLRequest(graphQLRequest);
Assert.NotNull(graphQLRequest.Variables);
Assert.Equal("JEDI", graphQLRequest.Variables.ep);
}
@@ -84,7 +73,6 @@ public void InlineFragmentsRequestFact() {
[Fact]
public void MetaFieldsRequestFact() {
var graphQLRequest = GraphQLRequestConsts.MetaFieldsRequest;
- AssertGraphQL.CorrectGraphQLRequest(graphQLRequest);
}
}
diff --git a/tests/GraphQL.Integration.Tests/GraphQLClientExtensions.cs b/tests/GraphQL.Integration.Tests/GraphQLClientExtensions.cs
index c7dd0568..ff71ee6d 100644
--- a/tests/GraphQL.Integration.Tests/GraphQLClientExtensions.cs
+++ b/tests/GraphQL.Integration.Tests/GraphQLClientExtensions.cs
@@ -10,14 +10,13 @@ public static class GraphQLClientExtensions
{
public static async Task AddMessageAsync(this GraphQLHttpClient client, string message)
{
- var graphQLRequest = new GraphQLRequest
- {
- Query = @"
+ var graphQLRequest = new GraphQLRequest(@"
mutation($input: MessageInputType){
addMessage(message: $input){
content
}
- }",
+ }")
+ {
Variables = new
{
input = new
@@ -30,5 +29,23 @@ public static async Task AddMessageAsync(this GraphQLHttpClient
};
return await client.SendMutationAsync(graphQLRequest).ConfigureAwait(false);
}
+
+ public static async Task JoinDeveloperUser(this GraphQLHttpClient client)
+ {
+ var graphQLRequest = new GraphQLRequest(@"
+ mutation($userId: String){
+ join(userId: $userId){
+ displayName
+ id
+ }
+ }")
+ {
+ Variables = new
+ {
+ userId = "1"
+ }
+ };
+ return await client.SendMutationAsync(graphQLRequest).ConfigureAwait(false);
+ }
}
}
diff --git a/tests/GraphQL.Integration.Tests/SubscriptionsTest.cs b/tests/GraphQL.Integration.Tests/SubscriptionsTest.cs
index 36804c95..635a0d1c 100644
--- a/tests/GraphQL.Integration.Tests/SubscriptionsTest.cs
+++ b/tests/GraphQL.Integration.Tests/SubscriptionsTest.cs
@@ -11,7 +11,6 @@
using Microsoft.Extensions.Logging;
using Xunit;
-
namespace GraphQL.Integration.Tests
{
public class SubscriptionsTest
@@ -24,7 +23,10 @@ public static IWebHost CreateServer(int port)
config["server.urls"] = $"http://localhost:{port}";
var host = new WebHostBuilder()
- .ConfigureLogging((ctx, logging) => logging.AddDebug())
+ .ConfigureLogging((ctx, logging) =>
+ {
+ logging.AddDebug();
+ })
.UseConfiguration(config)
.UseKestrel()
.UseStartup()
@@ -45,7 +47,7 @@ private GraphQLHttpClient GetGraphQLClient(int port)
=> new GraphQLHttpClient(new GraphQLHttpClientOptions
{
EndPoint = new Uri($"http://localhost:{port}/graphql"),
- } );
+ });
[Fact]
@@ -70,10 +72,7 @@ public async void AssertTestingHarness()
}
}";
- private readonly GraphQLRequest SubscriptionRequest = new GraphQLRequest
- {
- Query = SubscriptionQuery
- };
+ private readonly GraphQLRequest SubscriptionRequest = new GraphQLRequest(SubscriptionQuery);
[Fact]
public async void CanCreateObservableSubscription()
@@ -165,21 +164,16 @@ public async void CanReconnectWithSameObservable()
private const string SubscriptionQuery2 = @"
subscription {
- messageAdded{
- content
- from {
- displayName
- }
+ userJoined{
+ displayName
+ id
}
}";
- private readonly GraphQLRequest SubscriptionRequest2 = new GraphQLRequest
- {
- Query = SubscriptionQuery2
- };
+ private readonly GraphQLRequest SubscriptionRequest2 = new GraphQLRequest(SubscriptionQuery2);
[Fact]
- public async void CanConnectMultipleSubscriptionsSimultaneously()
+ public async void CanConnectTwoSubscriptionsSimultaneously()
{
var port = NetworkHelpers.GetFreeTcpPortNumber();
var callbackTester = new CallbackTester();
@@ -189,10 +183,12 @@ public async void CanConnectMultipleSubscriptionsSimultaneously()
var client = GetGraphQLClient(port);
Debug.WriteLine("creating subscription stream");
- IObservable subscription1 = client.CreateSubscriptionStream(SubscriptionRequest, callbackTester.Callback);
+ IObservable observable1 = client.CreateSubscriptionStream(SubscriptionRequest, callbackTester.Callback);
+ IObservable observable2 = client.CreateSubscriptionStream(SubscriptionRequest2, callbackTester2.Callback);
Debug.WriteLine("subscribing...");
- var tester = subscription1.SubscribeTester();
+ var tester = observable1.SubscribeTester();
+ var tester2 = observable2.SubscribeTester();
const string message1 = "Hello World";
var response = await client.AddMessageAsync(message1).ConfigureAwait(false);
@@ -202,42 +198,31 @@ public async void CanConnectMultipleSubscriptionsSimultaneously()
Assert.Equal(message1, (string)gqlResponse.Data.messageAdded.content.Value);
});
- IObservable subscription2 = client.CreateSubscriptionStream(SubscriptionRequest2, callbackTester2.Callback);
- var tester2 = subscription2.SubscribeTester();
- tester2.ShouldHaveReceivedUpdate(gqlResponse =>
- {
- Assert.Equal(message1, (string)gqlResponse.Data.messageAdded.content.Value);
- Assert.Equal("tester", (string)gqlResponse.Data.messageAdded.from.displayName.Value);
- }, TimeSpan.FromSeconds(10));
+ await Task.Delay(500); // ToDo: can be removed after https://github.com/graphql-dotnet/server/pull/199 was merged and released
+
+ response = await client.JoinDeveloperUser().ConfigureAwait(false);
+ Assert.Equal("developer", (string)response.Data.join.displayName.Value);
- const string message2 = "How are you?";
- response = await client.AddMessageAsync(message2).ConfigureAwait(false);
- Assert.Equal(message2, (string)response.Data.addMessage.content);
- tester.ShouldHaveReceivedUpdate(gqlResponse =>
- {
- Assert.Equal(message2, (string)gqlResponse.Data.messageAdded.content.Value);
- });
tester2.ShouldHaveReceivedUpdate(gqlResponse =>
{
- Assert.Equal(message2, (string)gqlResponse.Data.messageAdded.content.Value);
- Assert.Equal("tester", (string)gqlResponse.Data.messageAdded.from.displayName.Value);
+ Assert.Equal("1", (string)gqlResponse.Data.userJoined.id.Value);
+ Assert.Equal("developer", (string)gqlResponse.Data.userJoined.displayName.Value);
});
-
+
Debug.WriteLine("disposing subscription...");
- tester.Dispose();
+ tester2.Dispose();
const string message3 = "lorem ipsum dolor si amet";
response = await client.AddMessageAsync(message3).ConfigureAwait(false);
Assert.Equal(message3, (string)response.Data.addMessage.content);
- tester2.ShouldHaveReceivedUpdate(gqlResponse =>
+ tester.ShouldHaveReceivedUpdate(gqlResponse =>
{
Assert.Equal(message3, (string)gqlResponse.Data.messageAdded.content.Value);
- Assert.Equal("tester", (string)gqlResponse.Data.messageAdded.from.displayName.Value);
});
// disposing the client should complete the subscription
client.Dispose();
- tester2.ShouldHaveCompleted();
+ tester.ShouldHaveCompleted();
}
}
diff --git a/tests/IntegrationTestServer/ChatSchema/ChatMutation.cs b/tests/IntegrationTestServer/ChatSchema/ChatMutation.cs
index 7c2a2333..0ca1dfbd 100644
--- a/tests/IntegrationTestServer/ChatSchema/ChatMutation.cs
+++ b/tests/IntegrationTestServer/ChatSchema/ChatMutation.cs
@@ -16,7 +16,18 @@ public ChatMutation(IChat chat)
var message = chat.AddMessage(receivedMessage);
return message;
});
- }
+
+ Field("join",
+ arguments: new QueryArguments(
+ new QueryArgument { Name = "userId" }
+ ),
+ resolve: context =>
+ {
+ var userId = context.GetArgument("userId");
+ var userJoined = chat.Join(userId);
+ return userJoined;
+ });
+ }
}
public class MessageInputType : InputObjectGraphType
diff --git a/tests/IntegrationTestServer/ChatSchema/ChatSubscriptions.cs b/tests/IntegrationTestServer/ChatSchema/ChatSubscriptions.cs
index 3c8c9624..0ceb77a5 100644
--- a/tests/IntegrationTestServer/ChatSchema/ChatSubscriptions.cs
+++ b/tests/IntegrationTestServer/ChatSchema/ChatSubscriptions.cs
@@ -25,7 +25,15 @@ public ChatSubscriptions(IChat chat)
Subscriber = new EventStreamResolver(Subscribe)
});
- AddField(new EventStreamFieldType
+ AddField(new EventStreamFieldType
+ {
+ Name = "contentAdded",
+ Type = typeof(MessageType),
+ Resolver = new FuncFieldResolver(ResolveMessage),
+ Subscriber = new EventStreamResolver(Subscribe)
+ });
+
+ AddField(new EventStreamFieldType
{
Name = "messageAddedByUser",
Arguments = new QueryArguments(
@@ -35,7 +43,15 @@ public ChatSubscriptions(IChat chat)
Resolver = new FuncFieldResolver(ResolveMessage),
Subscriber = new EventStreamResolver(SubscribeById)
});
- }
+
+ AddField(new EventStreamFieldType
+ {
+ Name = "userJoined",
+ Type = typeof(MessageFromType),
+ Resolver = new FuncFieldResolver(context => context.Source as MessageFrom),
+ Subscriber = new EventStreamResolver(context => _chat.UserJoined())
+ });
+ }
private IObservable SubscribeById(ResolveEventStreamContext context)
{
diff --git a/tests/IntegrationTestServer/ChatSchema/IChat.cs b/tests/IntegrationTestServer/ChatSchema/IChat.cs
index 68c8e86b..e52c0432 100644
--- a/tests/IntegrationTestServer/ChatSchema/IChat.cs
+++ b/tests/IntegrationTestServer/ChatSchema/IChat.cs
@@ -11,16 +11,20 @@ public interface IChat
Message AddMessage(Message message);
- IObservable Messages(string user);
+ MessageFrom Join(string userId);
- Message AddMessage(ReceivedMessage message);
+ IObservable Messages(string user);
+ IObservable UserJoined();
+
+ Message AddMessage(ReceivedMessage message);
}
public class Chat : IChat
{
private readonly ISubject _messageStream = new ReplaySubject(1);
+ private readonly ISubject _userJoined = new Subject();
- public Chat()
+ public Chat()
{
AllMessages = new ConcurrentStack();
Users = new ConcurrentDictionary
@@ -58,9 +62,25 @@ public Message AddMessage(Message message)
AllMessages.Push(message);
_messageStream.OnNext(message);
return message;
- }
+ }
+
+ public MessageFrom Join(string userId)
+ {
+ if (!Users.TryGetValue(userId, out var displayName))
+ {
+ displayName = "(unknown)";
+ }
+
+ var joinedUser = new MessageFrom {
+ Id = userId,
+ DisplayName = displayName
+ };
- public IObservable Messages(string user)
+ _userJoined.OnNext(joinedUser);
+ return joinedUser;
+ }
+
+ public IObservable Messages(string user)
{
return _messageStream
.Select(message =>
@@ -75,5 +95,16 @@ public void AddError(Exception exception)
{
_messageStream.OnError(exception);
}
- }
+
+ public IObservable UserJoined()
+ {
+ return _userJoined.AsObservable();
+ }
+ }
+
+ public class User
+ {
+ public string Id { get; set; }
+ public string Name { get; set; }
+ }
}
diff --git a/tests/IntegrationTestServer/IntegrationTestServer.csproj b/tests/IntegrationTestServer/IntegrationTestServer.csproj
index 179554ff..cfba48cd 100644
--- a/tests/IntegrationTestServer/IntegrationTestServer.csproj
+++ b/tests/IntegrationTestServer/IntegrationTestServer.csproj
@@ -9,10 +9,12 @@
-
-
-
-
+
+
+
+
+
+
diff --git a/tests/IntegrationTestServer/Program.cs b/tests/IntegrationTestServer/Program.cs
index fd8448a8..9b0392b7 100644
--- a/tests/IntegrationTestServer/Program.cs
+++ b/tests/IntegrationTestServer/Program.cs
@@ -1,5 +1,6 @@
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Logging;
namespace IntegrationTestServer
{
@@ -12,6 +13,7 @@ public static void Main(string[] args)
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
- .UseStartup();
+ .UseStartup()
+ .ConfigureLogging((ctx, logging) => logging.SetMinimumLevel(LogLevel.Debug));
}
}
diff --git a/tests/IntegrationTestServer/Startup.cs b/tests/IntegrationTestServer/Startup.cs
index 0161c739..166bf202 100644
--- a/tests/IntegrationTestServer/Startup.cs
+++ b/tests/IntegrationTestServer/Startup.cs
@@ -1,6 +1,7 @@
using GraphQL.Server;
using GraphQL.Server.Ui.GraphiQL;
using GraphQL.Server.Ui.Voyager;
+using GraphQL.Server.Ui.Playground;
using IntegrationTestServer.ChatSchema;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
@@ -63,6 +64,10 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env)
GraphQLEndPoint = "/graphql",
Path = "/ui/voyager"
});
+ app.UseGraphQLPlayground(new GraphQLPlaygroundOptions
+ {
+ Path = "/ui/playground"
+ });
app.UseMvc();
}
}