From 041f327f87e4cca4ba49f6c37d0a7bfa303ca2de Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Tue, 20 Nov 2018 22:12:48 +0100 Subject: [PATCH 001/455] Observable Subscriptions (#80) * create observable subscription * create integration test server and test project * fix debug output * create basic unit test for subscription * make unit test politically correct * make sure all ClientWebSockets are disposed on disposing the GraphQlHttpClient * fix comment * Modified GraphQL.Client.csproj --- GraphQL.Client.sln | 16 +- src/GraphQL.Client/GraphQL.Client.csproj | 5 +- src/GraphQL.Client/Http/GraphQLHttpClient.cs | 37 ++- .../Http/GraphQLHttpObservableSubscription.cs | 268 ++++++++++++++++++ .../Http/GraphQLHttpSubscriptionResult.cs | 8 +- .../Http/HttpClientExtensions.cs | 10 + src/GraphQL.Client/IGraphQLClient.cs | 2 + .../Response/GraphQLSubscriptionResponse.cs | 2 +- .../GraphQL.Client.Tests.csproj | 4 +- .../GraphQL.Integration.Tests.csproj | 28 ++ .../GraphQLClientExtensions.cs | 34 +++ .../ObservableTester.cs | 151 ++++++++++ .../Properties/launchSettings.json | 27 ++ .../SubscriptionsTest.cs | 114 ++++++++ .../WebApplicationFactoryExtensions.cs | 19 ++ .../ChatSchema/ChatMutation.cs | 31 ++ .../ChatSchema/ChatQuery.cs | 13 + .../ChatSchema/ChatSchema.cs | 14 + .../ChatSchema/ChatSubscriptions.cs | 74 +++++ .../IntegrationTestServer/ChatSchema/IChat.cs | 79 ++++++ .../ChatSchema/Message.cs | 15 + .../ChatSchema/MessageFrom.cs | 9 + .../ChatSchema/MessageFromType.cs | 13 + .../ChatSchema/MessageType.cs | 21 ++ .../ChatSchema/ReceivedMessage.cs | 13 + .../IntegrationTestServer.csproj | 22 ++ tests/IntegrationTestServer/Program.cs | 17 ++ .../Properties/launchSettings.json | 28 ++ tests/IntegrationTestServer/Startup.cs | 69 +++++ 29 files changed, 1127 insertions(+), 16 deletions(-) create mode 100644 src/GraphQL.Client/Http/GraphQLHttpObservableSubscription.cs create mode 100644 tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj create mode 100644 tests/GraphQL.Integration.Tests/GraphQLClientExtensions.cs create mode 100644 tests/GraphQL.Integration.Tests/ObservableTester.cs create mode 100644 tests/GraphQL.Integration.Tests/Properties/launchSettings.json create mode 100644 tests/GraphQL.Integration.Tests/SubscriptionsTest.cs create mode 100644 tests/GraphQL.Integration.Tests/WebApplicationFactoryExtensions.cs create mode 100644 tests/IntegrationTestServer/ChatSchema/ChatMutation.cs create mode 100644 tests/IntegrationTestServer/ChatSchema/ChatQuery.cs create mode 100644 tests/IntegrationTestServer/ChatSchema/ChatSchema.cs create mode 100644 tests/IntegrationTestServer/ChatSchema/ChatSubscriptions.cs create mode 100644 tests/IntegrationTestServer/ChatSchema/IChat.cs create mode 100644 tests/IntegrationTestServer/ChatSchema/Message.cs create mode 100644 tests/IntegrationTestServer/ChatSchema/MessageFrom.cs create mode 100644 tests/IntegrationTestServer/ChatSchema/MessageFromType.cs create mode 100644 tests/IntegrationTestServer/ChatSchema/MessageType.cs create mode 100644 tests/IntegrationTestServer/ChatSchema/ReceivedMessage.cs create mode 100644 tests/IntegrationTestServer/IntegrationTestServer.csproj create mode 100644 tests/IntegrationTestServer/Program.cs create mode 100644 tests/IntegrationTestServer/Properties/launchSettings.json create mode 100644 tests/IntegrationTestServer/Startup.cs diff --git a/GraphQL.Client.sln b/GraphQL.Client.sln index 45a422a5..722a7d85 100644 --- a/GraphQL.Client.sln +++ b/GraphQL.Client.sln @@ -43,7 +43,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".circleci", ".circleci", "{ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{9413EC62-CDDE-4E77-9784-E1136EA5D1EE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GraphQL.Client.Sample", "samples\GraphQL.Client.Sample\GraphQL.Client.Sample.csproj", "{B21E97C3-F328-473F-A054-A4BF272B63F0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphQL.Client.Sample", "samples\GraphQL.Client.Sample\GraphQL.Client.Sample.csproj", "{B21E97C3-F328-473F-A054-A4BF272B63F0}" +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}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -71,6 +75,14 @@ Global {B21E97C3-F328-473F-A054-A4BF272B63F0}.Debug|Any CPU.Build.0 = Debug|Any CPU {B21E97C3-F328-473F-A054-A4BF272B63F0}.Release|Any CPU.ActiveCfg = Release|Any CPU {B21E97C3-F328-473F-A054-A4BF272B63F0}.Release|Any CPU.Build.0 = Release|Any CPU + {86BC3878-6549-4EF1-9672-B7C15A3FDF46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {86BC3878-6549-4EF1-9672-B7C15A3FDF46}.Debug|Any CPU.Build.0 = Debug|Any CPU + {86BC3878-6549-4EF1-9672-B7C15A3FDF46}.Release|Any CPU.ActiveCfg = Release|Any CPU + {86BC3878-6549-4EF1-9672-B7C15A3FDF46}.Release|Any CPU.Build.0 = Release|Any CPU + {618653E5-41C2-4F17-BE4F-F904267500D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {618653E5-41C2-4F17-BE4F-F904267500D4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {618653E5-41C2-4F17-BE4F-F904267500D4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {618653E5-41C2-4F17-BE4F-F904267500D4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -83,6 +95,8 @@ Global {6326E0E2-3F48-4BAF-80D3-47AED5EB647C} = {63F75859-4698-4EDE-8B70-4ACBB8BC425A} {C1406F03-650F-4633-887D-312943251919} = {63F75859-4698-4EDE-8B70-4ACBB8BC425A} {B21E97C3-F328-473F-A054-A4BF272B63F0} = {9413EC62-CDDE-4E77-9784-E1136EA5D1EE} + {86BC3878-6549-4EF1-9672-B7C15A3FDF46} = {0B0EDB0F-FF67-4B78-A8DB-B5C23E1FEE8C} + {618653E5-41C2-4F17-BE4F-F904267500D4} = {0B0EDB0F-FF67-4B78-A8DB-B5C23E1FEE8C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {387AC1AC-F90C-4EF8-955A-04D495C75AF4} diff --git a/src/GraphQL.Client/GraphQL.Client.csproj b/src/GraphQL.Client/GraphQL.Client.csproj index 99a24460..200758df 100644 --- a/src/GraphQL.Client/GraphQL.Client.csproj +++ b/src/GraphQL.Client/GraphQL.Client.csproj @@ -1,11 +1,11 @@ - + A GraphQL Client - netstandard1.3;netstandard2.0 + netstandard2.0 @@ -14,6 +14,7 @@ + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + false From 3724956e0166beef3fad9285e9d610207d091cfc Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Mon, 6 Apr 2020 10:31:41 +0200 Subject: [PATCH 243/455] add async assertions --- .../ObservableAssertions.cs | 98 ++++++++++++++++++- .../ObservableExtensions.cs | 24 ++++- 2 files changed, 117 insertions(+), 5 deletions(-) diff --git a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableAssertions.cs b/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableAssertions.cs index 4efa9c36..cd6bf808 100644 --- a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableAssertions.cs +++ b/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableAssertions.cs @@ -4,6 +4,7 @@ using System.Reactive; using System.Reactive.Linq; using System.Reactive.Threading.Tasks; +using System.Threading.Tasks; using FluentAssertions; using FluentAssertions.Execution; using FluentAssertions.Primitives; @@ -50,6 +51,39 @@ public AndWhichConstraint, IEnumerable, IEnumerable>>>(this, notifications); } + + /// + public async Task, IEnumerable>> PushAsync(int numberOfNotifications, TimeSpan timeout, + string because = "", params object[] becauseArgs) + { + IEnumerable notifications = new List(); + + try + { + notifications = await Observer.RecordedNotificationStream + .Select(r => r.Value) + .Dematerialize() + .Take(numberOfNotifications) + .Timeout(timeout) + .Catch(exception => Observable.Empty()) + .ToList() + .ToTask(); + + Execute.Assertion + .ForCondition(notifications.Any()) + .BecauseOf(because, becauseArgs) + .FailWith("Expected {context} to push at least one notification within {0}{reason}, but it did not.", timeout); + } + catch (Exception e) + { + Execute.Assertion + .BecauseOf(because, becauseArgs) + .FailWith("Expected {context} to push at least one notification, but failed with exception {1}.", timeout, e); + } + + return new AndWhichConstraint, IEnumerable>(this, notifications); + } + /// /// Asserts that at least notifications are pushed to the within the next 1 second.
/// This includes any previously recorded notifications since it has been created or cleared. @@ -57,6 +91,10 @@ public AndWhichConstraint, IEnumerable, IEnumerable>>> Push(int numberOfNotifications, string because = "", params object[] becauseArgs) => Push(numberOfNotifications, TimeSpan.FromSeconds(1), because, becauseArgs); + /// + public Task, IEnumerable>> PushAsync(int numberOfNotifications, string because = "", params object[] becauseArgs) + => PushAsync(numberOfNotifications, TimeSpan.FromSeconds(1), because, becauseArgs); + /// /// Asserts that at least 1 notification is pushed to the within the next 1 second.
/// This includes any previously recorded notifications since it has been created or cleared. @@ -64,6 +102,11 @@ public AndWhichConstraint, IEnumerable, IEnumerable>>> Push(string because = "", params object[] becauseArgs) => Push(1, TimeSpan.FromSeconds(1), because, becauseArgs); + /// + public Task, IEnumerable>> PushAsync(string because = "", params object[] becauseArgs) + => PushAsync(1, TimeSpan.FromSeconds(1), because, becauseArgs); + + /// /// Asserts that the does not receive any notifications within the specified .
/// This includes any previously recorded notifications since it has been created or cleared. @@ -93,6 +136,7 @@ public AndConstraint> NotPush(TimeSpan timeout, public AndConstraint> NotPush(string because = "", params object[] becauseArgs) => NotPush(TimeSpan.FromMilliseconds(100), because, becauseArgs); + /// /// Asserts that the observed by the fails within the specified . /// @@ -115,16 +159,40 @@ public AndWhichConstraint, Exception> Fail(TimeSp return new AndWhichConstraint, Exception>(this, exception); } + /// + public async Task, Exception>> FailAsync(TimeSpan timeout, + string because = "", params object[] becauseArgs) + { + var exception = await Observer.RecordedNotificationStream + .Timeout(timeout) + .Catch(Observable.Empty>>()) + .FirstOrDefaultAsync(recorded => recorded.Value.Kind == NotificationKind.OnError) + .Select(recorded => recorded.Value.Exception) + .ToTask(); + + Execute.Assertion + .ForCondition(exception != null) + .BecauseOf(because, becauseArgs) + .FailWith("Expected {context} to fail within {0}{reason}, but it did not.", timeout); + + return new AndWhichConstraint, Exception>(this, exception); + } + /// /// Asserts that the observed by the fails within the next 1 second. /// public AndWhichConstraint, Exception> Fail(string because = "", params object[] becauseArgs) => Fail(TimeSpan.FromSeconds(1), because, becauseArgs); + /// + public Task, Exception>> FailAsync(string because = "", params object[] becauseArgs) + => FailAsync(TimeSpan.FromSeconds(1), because, becauseArgs); + + /// /// Asserts that the observed by the completes within the specified . /// - public AndWhichConstraint, IEnumerable>>> Complete(TimeSpan timeout, + public AndConstraint> Complete(TimeSpan timeout, string because = "", params object[] becauseArgs) { @@ -140,15 +208,39 @@ public AndWhichConstraint, IEnumerable, IEnumerable>>>(this, Observer.RecordedNotifications); + return new AndConstraint>(this); + } + + /// + public async Task>> CompleteAsync(TimeSpan timeout, + string because = "", params object[] becauseArgs) + { + + bool completed = await Observer.RecordedNotificationStream + .Any(recorded => recorded.Value.Kind == NotificationKind.OnCompleted) + .Timeout(timeout) + .Catch(Observable.Return(false)) + .ToTask(); + + Execute.Assertion + .ForCondition(completed) + .BecauseOf(because, becauseArgs) + .FailWith("Expected {context} to complete within {0}{reason}, but it did not.", timeout); + + return new AndConstraint>(this); } /// /// Asserts that the observed by the completes within the next 1 second. /// - public AndWhichConstraint, IEnumerable>>> Complete(string because = "", params object[] becauseArgs) + public AndConstraint> Complete(string because = "", params object[] becauseArgs) => Complete(TimeSpan.FromSeconds(1), because, becauseArgs); + /// + public Task>> CompleteAsync(string because = "", params object[] becauseArgs) + => CompleteAsync(TimeSpan.FromSeconds(1), because, becauseArgs); + + /// /// Asserts that the observed by the does not complete within the specified . /// diff --git a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableExtensions.cs b/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableExtensions.cs index 1b477552..ec8c2137 100644 --- a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableExtensions.cs +++ b/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableExtensions.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Linq.Expressions; using System.Reactive; +using System.Threading.Tasks; using FluentAssertions; using FluentAssertions.Execution; using Microsoft.Reactive.Testing; @@ -58,7 +59,18 @@ public static AndWhichConstraint, IEnumerable( this AndWhichConstraint, IEnumerable>>> recorderConstraint) => - recorderConstraint.GetMessages().LastOrDefault(); + recorderConstraint.Subject.GetLastMessage(); + + /// + /// Extracts the last recorded message + /// + public static async Task GetLastMessageAsync( + this Task, IEnumerable>>>> + assertionTask) + { + var constraint = await assertionTask; + return constraint.GetLastMessage(); + } /// /// Extracts the recorded messages @@ -68,13 +80,21 @@ this AndWhichConstraint, IEnumerable recorderConstraint.Subject.GetMessages(); /// - /// Extracts the recorded messages from a number od recorded notifications + /// Extracts the recorded messages from a number of recorded notifications /// public static IEnumerable GetMessages( this IEnumerable>> recordedNotifications) => recordedNotifications .Where(r => r.Value.Kind == NotificationKind.OnNext) .Select(recorded => recorded.Value.Value); + /// + /// Extracts the recorded messages from a number od recorded notifications + /// + public static TPayload GetLastMessage( + this IEnumerable>> recordedNotifications) => + recordedNotifications.GetMessages().LastOrDefault(); + + /// /// Clears the recorded notifications on the underlying /// From 93c1f073229802fffcd8964096b9cc48b5b9156e Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Mon, 6 Apr 2020 10:39:33 +0200 Subject: [PATCH 244/455] run observable assertions async --- .../ObservableExtensions.cs | 4 +- .../WebsocketTests/Base.cs | 78 +++++++++++-------- 2 files changed, 49 insertions(+), 33 deletions(-) diff --git a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableExtensions.cs b/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableExtensions.cs index ec8c2137..da2d3987 100644 --- a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableExtensions.cs +++ b/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableExtensions.cs @@ -65,11 +65,11 @@ this AndWhichConstraint, IEnumerable public static async Task GetLastMessageAsync( - this Task, IEnumerable>>>> + this Task, IEnumerable>> assertionTask) { var constraint = await assertionTask; - return constraint.GetLastMessage(); + return constraint.Subject.LastOrDefault(); } /// diff --git a/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs b/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs index 12c2eea4..eb5bcc97 100644 --- a/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs +++ b/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Diagnostics; +using System.Linq; using System.Net.WebSockets; using System.Threading; using System.Threading.Tasks; @@ -137,21 +138,24 @@ public async void CanCreateObservableSubscription() Debug.WriteLine("subscribing..."); using var observer = observable.Observe(); - observer.Should().Push(1).GetLastMessage().Data.MessageAdded.Content.Should().Be(InitialMessage.Content); + var test = await observer.Should().PushAsync(1); + observer.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(InitialMessage.Content); const string message1 = "Hello World"; var response = await ChatClient.AddMessageAsync(message1); response.Data.AddMessage.Content.Should().Be(message1); - observer.Should().Push(2).GetLastMessage().Data.MessageAdded.Content.Should().Be(message1); + await observer.Should().PushAsync(2); + observer.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(message1); const string message2 = "lorem ipsum dolor si amet"; response = await ChatClient.AddMessageAsync(message2); response.Data.AddMessage.Content.Should().Be(message2); - observer.Should().Push(3).GetLastMessage().Data.MessageAdded.Content.Should().Be(message2); + await observer.Should().PushAsync(3); + observer.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(message2); // disposing the client should throw a TaskCanceledException on the subscription ChatClient.Dispose(); - observer.Should().Complete(); + await observer.Should().CompleteAsync(); } public class MessageAddedSubscriptionResult @@ -178,18 +182,21 @@ public async void CanReconnectWithSameObservable() callbackMonitor.Should().HaveBeenInvokedWithPayload(); await ChatClient.InitializeWebsocketConnection(); Debug.WriteLine("websocket connection initialized"); - observer.Should().Push(1).GetLastMessage().Data.MessageAdded.Content.Should().Be(InitialMessage.Content); + await observer.Should().PushAsync(1); + observer.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(InitialMessage.Content); const string message1 = "Hello World"; Debug.WriteLine($"adding message {message1}"); var response = await ChatClient.AddMessageAsync(message1).ConfigureAwait(true); response.Data.AddMessage.Content.Should().Be(message1); - observer.Should().Push(2).GetLastMessage().Data.MessageAdded.Content.Should().Be(message1); + await observer.Should().PushAsync(2); + observer.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(message1); const string message2 = "How are you?"; response = await ChatClient.AddMessageAsync(message2).ConfigureAwait(true); response.Data.AddMessage.Content.Should().Be(message2); - observer.Should().Push(3).GetLastMessage().Data.MessageAdded.Content.Should().Be(message2); + await observer.Should().PushAsync(3); + observer.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(message2); Debug.WriteLine("disposing subscription..."); observer.Dispose(); // does not close the websocket connection @@ -197,16 +204,18 @@ public async void CanReconnectWithSameObservable() Debug.WriteLine($"creating new subscription from thread {Thread.CurrentThread.ManagedThreadId} ..."); var observer2 = observable.Observe(); Debug.WriteLine($"waiting for payload on {Thread.CurrentThread.ManagedThreadId} ..."); - observer2.Should().Push(1).GetLastMessage().Data.MessageAdded.Content.Should().Be(message2); + await observer2.Should().PushAsync(1); + observer2.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(message2); const string message3 = "lorem ipsum dolor si amet"; response = await ChatClient.AddMessageAsync(message3).ConfigureAwait(true); response.Data.AddMessage.Content.Should().Be(message3); - observer2.Should().Push(2).GetLastMessage().Data.MessageAdded.Content.Should().Be(message3); + await observer2.Should().PushAsync(2); + observer2.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(message3); // disposing the client should complete the subscription ChatClient.Dispose(); - observer2.Should().Complete(); + await observer2.Should().CompleteAsync(); observer2.Dispose(); } @@ -257,7 +266,9 @@ public async void CanConnectTwoSubscriptionsSimultaneously() const string message1 = "Hello World"; var response = await ChatClient.AddMessageAsync(message1); response.Data.AddMessage.Content.Should().Be(message1); - messagesMonitor.Should().Push(2).GetLastMessage().Data.MessageAdded.Content.Should().Be(message1); + await messagesMonitor.Should().PushAsync(2); + messagesMonitor.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(message1); + joinedMonitor.Should().NotPush(); messagesMonitor.Clear(); joinedMonitor.Clear(); @@ -265,12 +276,13 @@ public async void CanConnectTwoSubscriptionsSimultaneously() var joinResponse = await ChatClient.JoinDeveloperUser(); joinResponse.Data.Join.DisplayName.Should().Be("developer", "because that's the display name of user \"1\""); - var payload = joinedMonitor.Should().Push().GetLastMessage(); + var payload = await joinedMonitor.Should().PushAsync().GetLastMessageAsync(); using (new AssertionScope()) { payload.Data.UserJoined.Id.Should().Be("1", "because that's the id we sent with our mutation request"); payload.Data.UserJoined.DisplayName.Should().Be("developer", "because that's the display name of user \"1\""); } + messagesMonitor.Should().NotPush(); messagesMonitor.Clear(); joinedMonitor.Clear(); @@ -281,12 +293,12 @@ public async void CanConnectTwoSubscriptionsSimultaneously() const string message3 = "lorem ipsum dolor si amet"; response = await ChatClient.AddMessageAsync(message3); response.Data.AddMessage.Content.Should().Be(message3); - messagesMonitor.Should().Push() - .GetLastMessage().Data.MessageAdded.Content.Should().Be(message3); + var msg = await messagesMonitor.Should().PushAsync().GetLastMessageAsync(); + msg.Data.MessageAdded.Content.Should().Be(message3); // disposing the client should complete the subscription ChatClient.Dispose(); - messagesMonitor.Should().Complete(); + await messagesMonitor.Should().CompleteAsync(); } @@ -327,12 +339,14 @@ public async void CanHandleConnectionTimeout() // clear the collection so the next tests on the collection work as expected websocketStates.Clear(); - observer.Should().Push(1).GetLastMessage().Data.MessageAdded.Content.Should().Be(InitialMessage.Content); + await observer.Should().PushAsync(1); + observer.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(InitialMessage.Content); const string message1 = "Hello World"; var response = await ChatClient.AddMessageAsync(message1); response.Data.AddMessage.Content.Should().Be(message1); - observer.Should().Push(2).GetLastMessage().Data.MessageAdded.Content.Should().Be(message1); + await observer.Should().PushAsync(2); + observer.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(message1); Debug.WriteLine("stopping web host..."); await Fixture.ShutdownServer(); @@ -347,7 +361,8 @@ public async void CanHandleConnectionTimeout() Debug.WriteLine("web host started"); reconnectBlocker.Set(); callbackMonitor.Should().HaveBeenInvokedWithPayload(3.Seconds()); - observer.Should().Push(3).GetLastMessage().Data.MessageAdded.Content.Should().Be(InitialMessage.Content); + await observer.Should().PushAsync(3); + observer.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(InitialMessage.Content); websocketStates.Should().ContainInOrder( GraphQLWebsocketConnectionState.Disconnected, @@ -356,7 +371,7 @@ public async void CanHandleConnectionTimeout() // disposing the client should complete the subscription ChatClient.Dispose(); - observer.Should().Complete(5.Seconds()); + await observer.Should().CompleteAsync(5.Seconds()); } } @@ -380,14 +395,14 @@ public async void CanHandleSubscriptionError() Debug.WriteLine("subscribing..."); using var observer = observable.Observe(); - observer.Should().Push(1, 3.Seconds()) - .GetLastMessage() - .Errors.Should().ContainSingle(); - observer.Should().Complete(); + + await observer.Should().PushAsync(); + observer.RecordedMessages.Last().Errors.Should().ContainSingle(); + + await observer.Should().CompleteAsync(); ChatClient.Dispose(); } - [Fact] public async void CanHandleQueryErrorInSubscription() { @@ -407,13 +422,14 @@ public async void CanHandleQueryErrorInSubscription() ); Debug.WriteLine("subscribing..."); - using (var observer = observable.Observe()) - { - observer.Should().Push() - .GetLastMessage().Errors.Should().ContainSingle(); - observer.Should().Complete(); - ChatClient.Dispose(); - } + + using var observer = observable.Observe(); + + await observer.Should().PushAsync(); + observer.RecordedMessages.Last().Errors.Should().ContainSingle(); + + await observer.Should().CompleteAsync(); + ChatClient.Dispose(); } } From c06cb6eb7074b7b10e8c05fda7e274d7becb102d Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Mon, 6 Apr 2020 10:45:34 +0200 Subject: [PATCH 245/455] move assertion outside of try..catch --- .../FluentAssertions.Reactive/ObservableAssertions.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableAssertions.cs b/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableAssertions.cs index cd6bf808..28640229 100644 --- a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableAssertions.cs +++ b/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableAssertions.cs @@ -68,11 +68,6 @@ public async Task, IEnumerable .Catch(exception => Observable.Empty()) .ToList() .ToTask(); - - Execute.Assertion - .ForCondition(notifications.Any()) - .BecauseOf(because, becauseArgs) - .FailWith("Expected {context} to push at least one notification within {0}{reason}, but it did not.", timeout); } catch (Exception e) { @@ -80,6 +75,11 @@ public async Task, IEnumerable .BecauseOf(because, becauseArgs) .FailWith("Expected {context} to push at least one notification, but failed with exception {1}.", timeout, e); } + + Execute.Assertion + .ForCondition(notifications.Any()) + .BecauseOf(because, becauseArgs) + .FailWith("Expected {context} to push at least one notification within {0}{reason}, but it did not.", timeout); return new AndWhichConstraint, IEnumerable>(this, notifications); } From 30c249c83a9c6c9099bf663602cc104f294f7904 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Mon, 6 Apr 2020 10:50:10 +0200 Subject: [PATCH 246/455] set default timeout to 10s --- .../FluentAssertions.Reactive/ObservableAssertions.cs | 4 ++-- tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableAssertions.cs b/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableAssertions.cs index 28640229..8b321fde 100644 --- a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableAssertions.cs +++ b/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableAssertions.cs @@ -89,11 +89,11 @@ public async Task, IEnumerable /// This includes any previously recorded notifications since it has been created or cleared. /// public AndWhichConstraint, IEnumerable>>> Push(int numberOfNotifications, string because = "", params object[] becauseArgs) - => Push(numberOfNotifications, TimeSpan.FromSeconds(1), because, becauseArgs); + => Push(numberOfNotifications, TimeSpan.FromSeconds(10), because, becauseArgs); /// public Task, IEnumerable>> PushAsync(int numberOfNotifications, string because = "", params object[] becauseArgs) - => PushAsync(numberOfNotifications, TimeSpan.FromSeconds(1), because, becauseArgs); + => PushAsync(numberOfNotifications, TimeSpan.FromSeconds(10), because, becauseArgs); /// /// Asserts that at least 1 notification is pushed to the within the next 1 second.
diff --git a/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs b/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs index eb5bcc97..7f103a9a 100644 --- a/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs +++ b/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs @@ -138,7 +138,7 @@ public async void CanCreateObservableSubscription() Debug.WriteLine("subscribing..."); using var observer = observable.Observe(); - var test = await observer.Should().PushAsync(1); + await observer.Should().PushAsync(1); observer.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(InitialMessage.Content); const string message1 = "Hello World"; From 9e676ebb8834ff54e998c918d6061b58635b14e5 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Mon, 6 Apr 2020 11:03:12 +0200 Subject: [PATCH 247/455] consolidate ObservableExtensions and ObservableAssertions --- .../ObservableAssertions.cs | 43 +++++++++++-------- .../ObservableExtensions.cs | 28 +++++------- .../WebsocketTests/Base.cs | 3 +- 3 files changed, 36 insertions(+), 38 deletions(-) diff --git a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableAssertions.cs b/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableAssertions.cs index 8b321fde..fd266d0b 100644 --- a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableAssertions.cs +++ b/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableAssertions.cs @@ -31,27 +31,38 @@ protected internal ObservableAssertions(FluentTestObserver observer): /// Asserts that at least notifications were pushed to the within the specified .
/// This includes any previously recorded notifications since it has been created or cleared. ///
- public AndWhichConstraint, IEnumerable>>> Push(int numberOfNotifications, TimeSpan timeout, + public AndWhichConstraint, IEnumerable> Push(int numberOfNotifications, TimeSpan timeout, string because = "", params object[] becauseArgs) { - var notifications = Observer.RecordedNotificationStream - .Where(recorded => recorded.Value.Kind == NotificationKind.OnNext) - .Take(numberOfNotifications) - .Timeout(timeout) - .Catch(Observable.Empty>>()) - .ToList() - .ToTask() - .ExecuteInDefaultSynchronizationContext(); + IEnumerable notifications = new List(); + + try + { + notifications = Observer.RecordedNotificationStream + .Select(r => r.Value) + .Dematerialize() + .Take(numberOfNotifications) + .Timeout(timeout) + .Catch(exception => Observable.Empty()) + .ToList() + .ToTask() + .ExecuteInDefaultSynchronizationContext(); + } + catch (Exception e) + { + Execute.Assertion + .BecauseOf(because, becauseArgs) + .FailWith("Expected {context} to push at least one notification, but failed with exception {1}.", timeout, e); + } Execute.Assertion .ForCondition(notifications.Any()) .BecauseOf(because, becauseArgs) .FailWith("Expected {context} to push at least one notification within {0}{reason}, but it did not.", timeout); - return new AndWhichConstraint, IEnumerable>>>(this, notifications); + return new AndWhichConstraint, IEnumerable>(this, notifications); } - /// public async Task, IEnumerable>> PushAsync(int numberOfNotifications, TimeSpan timeout, string because = "", params object[] becauseArgs) @@ -88,7 +99,7 @@ public async Task, IEnumerable /// Asserts that at least notifications are pushed to the within the next 1 second.
/// This includes any previously recorded notifications since it has been created or cleared. ///
- public AndWhichConstraint, IEnumerable>>> Push(int numberOfNotifications, string because = "", params object[] becauseArgs) + public AndWhichConstraint, IEnumerable> Push(int numberOfNotifications, string because = "", params object[] becauseArgs) => Push(numberOfNotifications, TimeSpan.FromSeconds(10), because, becauseArgs); /// @@ -99,14 +110,13 @@ public Task, IEnumerable within the next 1 second.
/// This includes any previously recorded notifications since it has been created or cleared. ///
- public AndWhichConstraint, IEnumerable>>> Push(string because = "", params object[] becauseArgs) + public AndWhichConstraint, IEnumerable> Push(string because = "", params object[] becauseArgs) => Push(1, TimeSpan.FromSeconds(1), because, becauseArgs); /// public Task, IEnumerable>> PushAsync(string because = "", params object[] becauseArgs) => PushAsync(1, TimeSpan.FromSeconds(1), because, becauseArgs); - /// /// Asserts that the does not receive any notifications within the specified .
/// This includes any previously recorded notifications since it has been created or cleared. @@ -136,7 +146,6 @@ public AndConstraint> NotPush(TimeSpan timeout, public AndConstraint> NotPush(string because = "", params object[] becauseArgs) => NotPush(TimeSpan.FromMilliseconds(100), because, becauseArgs); - /// /// Asserts that the observed by the fails within the specified . /// @@ -188,14 +197,12 @@ public AndWhichConstraint, Exception> Fail(string public Task, Exception>> FailAsync(string because = "", params object[] becauseArgs) => FailAsync(TimeSpan.FromSeconds(1), because, becauseArgs); - /// /// Asserts that the observed by the completes within the specified . /// public AndConstraint> Complete(TimeSpan timeout, string because = "", params object[] becauseArgs) { - bool completed = Observer.RecordedNotificationStream .Any(recorded => recorded.Value.Kind == NotificationKind.OnCompleted) .Timeout(timeout) @@ -215,7 +222,6 @@ public AndConstraint> Complete(TimeSpan timeout, public async Task>> CompleteAsync(TimeSpan timeout, string because = "", params object[] becauseArgs) { - bool completed = await Observer.RecordedNotificationStream .Any(recorded => recorded.Value.Kind == NotificationKind.OnCompleted) .Timeout(timeout) @@ -240,7 +246,6 @@ public AndConstraint> Complete(string because = " public Task>> CompleteAsync(string because = "", params object[] becauseArgs) => CompleteAsync(TimeSpan.FromSeconds(1), because, becauseArgs); - /// /// Asserts that the observed by the does not complete within the specified . /// diff --git a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableExtensions.cs b/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableExtensions.cs index da2d3987..a9dfce46 100644 --- a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableExtensions.cs +++ b/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableExtensions.cs @@ -20,13 +20,13 @@ public static class ObservableExtensions /// /// Asserts that the recorded messages contain at lease one item which matches the /// - public static AndWhichConstraint, IEnumerable>>> WithMessage( - this AndWhichConstraint, IEnumerable>>> recorderConstraint, Expression> predicate) + public static AndWhichConstraint, IEnumerable> WithMessage( + this AndWhichConstraint, IEnumerable> recorderConstraint, Expression> predicate) { if (predicate is null) throw new ArgumentNullException(nameof(predicate)); var compiledPredicate = predicate.Compile(); - bool match = recorderConstraint.GetMessages().Any(compiledPredicate); + bool match = recorderConstraint.Subject.Any(compiledPredicate); Execute.Assertion .ForCondition(match) @@ -38,8 +38,8 @@ public static AndWhichConstraint, IEnumerable /// Asserts that the last recorded message matches the ///
- public static AndWhichConstraint, IEnumerable>>> WithLastMessage( - this AndWhichConstraint, IEnumerable>>> recorderConstraint, Expression> predicate) + public static AndWhichConstraint, IEnumerable> WithLastMessage( + this AndWhichConstraint, IEnumerable> recorderConstraint, Expression> predicate) { if (predicate is null) throw new ArgumentNullException(nameof(predicate)); @@ -57,9 +57,9 @@ public static AndWhichConstraint, IEnumerable public static TPayload GetLastMessage( - this AndWhichConstraint, IEnumerable>>> + this AndWhichConstraint, IEnumerable> recorderConstraint) => - recorderConstraint.Subject.GetLastMessage(); + recorderConstraint.Subject.LastOrDefault(); /// /// Extracts the last recorded message @@ -71,14 +71,7 @@ this Task, IEnumerable - /// Extracts the recorded messages - /// - public static IEnumerable GetMessages( - this AndWhichConstraint, IEnumerable>>> - recorderConstraint) => recorderConstraint.Subject.GetMessages(); - + /// /// Extracts the recorded messages from a number of recorded notifications /// @@ -88,13 +81,12 @@ public static IEnumerable GetMessages( .Select(recorded => recorded.Value.Value); /// - /// Extracts the recorded messages from a number od recorded notifications + /// Extracts the last recorded message from a number of recorded notifications /// public static TPayload GetLastMessage( this IEnumerable>> recordedNotifications) => recordedNotifications.GetMessages().LastOrDefault(); - - + /// /// Clears the recorded notifications on the underlying /// diff --git a/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs b/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs index 7f103a9a..426df661 100644 --- a/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs +++ b/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs @@ -261,7 +261,8 @@ public async void CanConnectTwoSubscriptionsSimultaneously() var messagesMonitor = observable1.Observe(); var joinedMonitor = observable2.Observe(); - messagesMonitor.Should().Push(1).GetLastMessage().Data.MessageAdded.Content.Should().Be(InitialMessage.Content); + await messagesMonitor.Should().PushAsync(1); + messagesMonitor.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(InitialMessage.Content); const string message1 = "Hello World"; var response = await ChatClient.AddMessageAsync(message1); From e3606c27134c74c25356a6e4ccd5b6b9a4f47882 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Mon, 6 Apr 2020 11:16:35 +0200 Subject: [PATCH 248/455] fix Assertion messages, add doc comments --- .../ObservableAssertions.cs | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableAssertions.cs b/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableAssertions.cs index fd266d0b..e6e86fb3 100644 --- a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableAssertions.cs +++ b/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableAssertions.cs @@ -26,11 +26,15 @@ protected internal ObservableAssertions(FluentTestObserver observer): } protected override string Identifier => "Subscription"; - + /// /// Asserts that at least notifications were pushed to the within the specified .
- /// This includes any previously recorded notifications since it has been created or cleared. - ///
+ /// This includes any previously recorded notifications since it has been created or cleared. + ///
+ /// the number of notifications the observer should have recorded by now + /// the maximum time to wait for the notifications to arrive + /// + /// public AndWhichConstraint, IEnumerable> Push(int numberOfNotifications, TimeSpan timeout, string because = "", params object[] becauseArgs) { @@ -52,13 +56,15 @@ public AndWhichConstraint, IEnumerable> { Execute.Assertion .BecauseOf(because, becauseArgs) - .FailWith("Expected {context} to push at least one notification, but failed with exception {1}.", timeout, e); + .FailWith("Expected {context} to push at least {0} notification{1}, but failed with exception {2}.", + numberOfNotifications, numberOfNotifications == 1 ? "" : "s", e); } Execute.Assertion .ForCondition(notifications.Any()) .BecauseOf(because, becauseArgs) - .FailWith("Expected {context} to push at least one notification within {0}{reason}, but it did not.", timeout); + .FailWith("Expected {context} to push at least {0} notification{1} within {2}{reason}, but it did not.", + numberOfNotifications, numberOfNotifications == 1 ? "" : "s", timeout); return new AndWhichConstraint, IEnumerable>(this, notifications); } @@ -84,13 +90,15 @@ public async Task, IEnumerable { Execute.Assertion .BecauseOf(because, becauseArgs) - .FailWith("Expected {context} to push at least one notification, but failed with exception {1}.", timeout, e); + .FailWith("Expected {context} to push at least {0} notification{1}, but failed with exception {2}.", + numberOfNotifications, numberOfNotifications == 1 ? "" : "s", e); } - + Execute.Assertion .ForCondition(notifications.Any()) .BecauseOf(because, becauseArgs) - .FailWith("Expected {context} to push at least one notification within {0}{reason}, but it did not.", timeout); + .FailWith("Expected {context} to push at least {0} notification{1} within {2}{reason}, but it did not.", + numberOfNotifications, numberOfNotifications == 1 ? "" : "s", timeout); return new AndWhichConstraint, IEnumerable>(this, notifications); } @@ -99,6 +107,9 @@ public async Task, IEnumerable /// Asserts that at least notifications are pushed to the within the next 1 second.
/// This includes any previously recorded notifications since it has been created or cleared. ///
+ /// the number of notifications the observer should have recorded by now + /// + /// public AndWhichConstraint, IEnumerable> Push(int numberOfNotifications, string because = "", params object[] becauseArgs) => Push(numberOfNotifications, TimeSpan.FromSeconds(10), because, becauseArgs); From 014551454a7157182fe82a8b700c48c2117776d1 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Mon, 6 Apr 2020 11:48:38 +0200 Subject: [PATCH 249/455] optimize assertion message for push assertions --- .../ObservableAssertions.cs | 38 +++++++++---------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableAssertions.cs b/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableAssertions.cs index e6e86fb3..6fb4c5f5 100644 --- a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableAssertions.cs +++ b/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableAssertions.cs @@ -38,7 +38,10 @@ protected internal ObservableAssertions(FluentTestObserver observer): public AndWhichConstraint, IEnumerable> Push(int numberOfNotifications, TimeSpan timeout, string because = "", params object[] becauseArgs) { - IEnumerable notifications = new List(); + IList notifications = new List(); + var assertion = Execute.Assertion + .WithExpectation($"Expected {{context}} to push at least {numberOfNotifications} {(numberOfNotifications == 1 ? "notification" : "notifications")}, ") + .BecauseOf(because, becauseArgs); try { @@ -54,17 +57,12 @@ public AndWhichConstraint, IEnumerable> } catch (Exception e) { - Execute.Assertion - .BecauseOf(because, becauseArgs) - .FailWith("Expected {context} to push at least {0} notification{1}, but failed with exception {2}.", - numberOfNotifications, numberOfNotifications == 1 ? "" : "s", e); + assertion.FailWith("but failed with exception {0}.", e); } - - Execute.Assertion - .ForCondition(notifications.Any()) - .BecauseOf(because, becauseArgs) - .FailWith("Expected {context} to push at least {0} notification{1} within {2}{reason}, but it did not.", - numberOfNotifications, numberOfNotifications == 1 ? "" : "s", timeout); + + assertion + .ForCondition(notifications.Count < numberOfNotifications) + .FailWith("but {0} were received.", numberOfNotifications); return new AndWhichConstraint, IEnumerable>(this, notifications); } @@ -73,7 +71,10 @@ public AndWhichConstraint, IEnumerable> public async Task, IEnumerable>> PushAsync(int numberOfNotifications, TimeSpan timeout, string because = "", params object[] becauseArgs) { - IEnumerable notifications = new List(); + IList notifications = new List(); + var assertion = Execute.Assertion + .WithExpectation($"Expected {{context}} to push at least {numberOfNotifications} {(numberOfNotifications == 1 ? "notification" : "notifications")}, ") + .BecauseOf(because, becauseArgs); try { @@ -88,17 +89,12 @@ public async Task, IEnumerable } catch (Exception e) { - Execute.Assertion - .BecauseOf(because, becauseArgs) - .FailWith("Expected {context} to push at least {0} notification{1}, but failed with exception {2}.", - numberOfNotifications, numberOfNotifications == 1 ? "" : "s", e); + assertion.FailWith("but failed with exception {0}.", e); } - Execute.Assertion - .ForCondition(notifications.Any()) - .BecauseOf(because, becauseArgs) - .FailWith("Expected {context} to push at least {0} notification{1} within {2}{reason}, but it did not.", - numberOfNotifications, numberOfNotifications == 1 ? "" : "s", timeout); + assertion + .ForCondition(notifications.Count < numberOfNotifications) + .FailWith("but {0} were received.", numberOfNotifications); return new AndWhichConstraint, IEnumerable>(this, notifications); } From c8f257084a78d9381c789e39e529dddf9afd911e Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Mon, 6 Apr 2020 11:53:12 +0200 Subject: [PATCH 250/455] fix assertion message again --- .../FluentAssertions.Reactive/ObservableAssertions.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableAssertions.cs b/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableAssertions.cs index 6fb4c5f5..c65d4f79 100644 --- a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableAssertions.cs +++ b/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableAssertions.cs @@ -57,12 +57,12 @@ public AndWhichConstraint, IEnumerable> } catch (Exception e) { - assertion.FailWith("but failed with exception {0}.", e); + assertion.FailWith("but it failed with exception {0}.", e); } assertion .ForCondition(notifications.Count < numberOfNotifications) - .FailWith("but {0} were received.", numberOfNotifications); + .FailWith("but {0} were received.", notifications.Count); return new AndWhichConstraint, IEnumerable>(this, notifications); } @@ -89,12 +89,12 @@ public async Task, IEnumerable } catch (Exception e) { - assertion.FailWith("but failed with exception {0}.", e); + assertion.FailWith("but it failed with exception {0}.", e); } assertion .ForCondition(notifications.Count < numberOfNotifications) - .FailWith("but {0} were received.", numberOfNotifications); + .FailWith("but {0} were received.", notifications.Count); return new AndWhichConstraint, IEnumerable>(this, notifications); } From b19f627af1796ee6d9c1661774396f54752eb7ea Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Mon, 6 Apr 2020 11:54:22 +0200 Subject: [PATCH 251/455] and again --- .../FluentAssertions.Reactive/ObservableAssertions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableAssertions.cs b/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableAssertions.cs index c65d4f79..6f69fa1f 100644 --- a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableAssertions.cs +++ b/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableAssertions.cs @@ -62,7 +62,7 @@ public AndWhichConstraint, IEnumerable> assertion .ForCondition(notifications.Count < numberOfNotifications) - .FailWith("but {0} were received.", notifications.Count); + .FailWith("but {0} were received within {1}.", notifications.Count, timeout); return new AndWhichConstraint, IEnumerable>(this, notifications); } @@ -94,7 +94,7 @@ public async Task, IEnumerable assertion .ForCondition(notifications.Count < numberOfNotifications) - .FailWith("but {0} were received.", notifications.Count); + .FailWith("but {0} were received within {1}.", notifications.Count, timeout); return new AndWhichConstraint, IEnumerable>(this, notifications); } From ba7a991d4d9f6ecb77ab2ec7b7c3209f586dcbad Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Mon, 6 Apr 2020 11:56:31 +0200 Subject: [PATCH 252/455] fix condition --- .../FluentAssertions.Reactive/ObservableAssertions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableAssertions.cs b/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableAssertions.cs index 6f69fa1f..c30f986c 100644 --- a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableAssertions.cs +++ b/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableAssertions.cs @@ -61,7 +61,7 @@ public AndWhichConstraint, IEnumerable> } assertion - .ForCondition(notifications.Count < numberOfNotifications) + .ForCondition(notifications.Count >= numberOfNotifications) .FailWith("but {0} were received within {1}.", notifications.Count, timeout); return new AndWhichConstraint, IEnumerable>(this, notifications); @@ -93,7 +93,7 @@ public async Task, IEnumerable } assertion - .ForCondition(notifications.Count < numberOfNotifications) + .ForCondition(notifications.Count >= numberOfNotifications) .FailWith("but {0} were received within {1}.", notifications.Count, timeout); return new AndWhichConstraint, IEnumerable>(this, notifications); From feb236205ac7ceec465087e9eeca5da7c2b6f67e Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Thu, 9 Apr 2020 22:52:58 +0200 Subject: [PATCH 253/455] update reactive assertions --- .../FluentTestObserver.cs | 62 ++++-- ...bleAssertions.cs => ReactiveAssertions.cs} | 196 ++++++++++-------- ...bleExtensions.cs => ReactiveExtensions.cs} | 27 ++- .../RollingReplaySubject.cs | 2 +- 4 files changed, 181 insertions(+), 106 deletions(-) rename tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/{ObservableAssertions.cs => ReactiveAssertions.cs} (54%) rename tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/{ObservableExtensions.cs => ReactiveExtensions.cs} (69%) diff --git a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/FluentTestObserver.cs b/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/FluentTestObserver.cs index 521af220..a9350130 100644 --- a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/FluentTestObserver.cs +++ b/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/FluentTestObserver.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; -using System.Diagnostics; +using System.Linq; using System.Reactive; using System.Reactive.Concurrency; +using System.Reactive.Disposables; using System.Reactive.Linq; -using System.Threading; using Microsoft.Reactive.Testing; namespace GraphQL.Client.Tests.Common.FluentAssertions.Reactive @@ -16,7 +16,7 @@ namespace GraphQL.Client.Tests.Common.FluentAssertions.Reactive public class FluentTestObserver : IObserver, IDisposable { private readonly IDisposable _subscription; - private readonly EventLoopScheduler _observeScheduler = new EventLoopScheduler(); + private readonly IScheduler _observeScheduler; private readonly RollingReplaySubject>> _rollingReplaySubject = new RollingReplaySubject>>(); /// @@ -33,13 +33,29 @@ public class FluentTestObserver : IObserver, IDisposable /// The recorded s /// public IEnumerable>> RecordedNotifications => - _rollingReplaySubject.Snapshot(); + _rollingReplaySubject.GetSnapshot(); /// /// The recorded messages /// public IEnumerable RecordedMessages => RecordedNotifications.GetMessages(); + + /// + /// The exception + /// + public Exception Error => + RecordedNotifications + .Where(r => r.Value.Kind == NotificationKind.OnError) + .Select(r => r.Value.Exception) + .FirstOrDefault(); + + /// + /// The recorded messages + /// + public bool Completed => + RecordedNotifications + .Any(r => r.Value.Kind == NotificationKind.OnCompleted); /// /// Creates a new which subscribes to the supplied @@ -48,10 +64,30 @@ public class FluentTestObserver : IObserver, IDisposable public FluentTestObserver(IObservable subject) { Subject = subject; - _observeScheduler.Schedule(() => - Debug.WriteLine($"Observe scheduler thread id: {Thread.CurrentThread.ManagedThreadId}")); + _observeScheduler = new EventLoopScheduler(); + _subscription = new CompositeDisposable(); subject.ObserveOn(_observeScheduler).Subscribe(this); + } + + /// + /// Creates a new which subscribes to the supplied + /// + /// the under test + public FluentTestObserver(IObservable subject, IScheduler scheduler) + { + Subject = subject; + _observeScheduler = scheduler; + _subscription = subject.ObserveOn(scheduler).Subscribe(this); + } - _subscription = subject.ObserveOn(_observeScheduler).Subscribe(this); + /// + /// Creates a new which subscribes to the supplied + /// + /// the under test + public FluentTestObserver(IObservable subject, TestScheduler testScheduler) + { + Subject = subject; + _observeScheduler = testScheduler; + _subscription = subject.ObserveOn(Scheduler.CurrentThread).Subscribe(this); } /// @@ -60,8 +96,11 @@ public FluentTestObserver(IObservable subject) public void Clear() => _rollingReplaySubject.Clear(); /// - public void OnNext(TPayload value) => - _rollingReplaySubject.OnNext(new Recorded>(_observeScheduler.Now.UtcTicks, Notification.CreateOnNext(value))); + public void OnNext(TPayload value) + { + _rollingReplaySubject.OnNext( + new Recorded>(_observeScheduler.Now.UtcTicks, Notification.CreateOnNext(value))); + } /// public void OnError(Exception exception) => @@ -75,14 +114,13 @@ public void OnCompleted() => public void Dispose() { _subscription?.Dispose(); - _observeScheduler.Dispose(); _rollingReplaySubject?.Dispose(); } /// - /// Returns an object that can be used to assert the observed + /// Returns an object that can be used to assert the observed /// /// - public ObservableAssertions Should() => new ObservableAssertions(this); + public ReactiveAssertions Should() => new ReactiveAssertions(this); } } diff --git a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableAssertions.cs b/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ReactiveAssertions.cs similarity index 54% rename from tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableAssertions.cs rename to tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ReactiveAssertions.cs index c30f986c..b9aa410c 100644 --- a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableAssertions.cs +++ b/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ReactiveAssertions.cs @@ -8,6 +8,7 @@ using FluentAssertions; using FluentAssertions.Execution; using FluentAssertions.Primitives; +using FluentAssertions.Specialized; using Microsoft.Reactive.Testing; namespace GraphQL.Client.Tests.Common.FluentAssertions.Reactive @@ -16,11 +17,12 @@ namespace GraphQL.Client.Tests.Common.FluentAssertions.Reactive /// Provides methods to assert an observed by a /// /// - public class ObservableAssertions : ReferenceTypeAssertions, ObservableAssertions> + public class ReactiveAssertions : ReferenceTypeAssertions, ReactiveAssertions> { + private readonly IExtractExceptions extractor = new AggregateExceptionExtractor(); public FluentTestObserver Observer { get; } - protected internal ObservableAssertions(FluentTestObserver observer): base(observer.Subject) + protected internal ReactiveAssertions(FluentTestObserver observer): base(observer.Subject) { Observer = observer; } @@ -35,12 +37,12 @@ protected internal ObservableAssertions(FluentTestObserver observer): /// the maximum time to wait for the notifications to arrive /// /// - public AndWhichConstraint, IEnumerable> Push(int numberOfNotifications, TimeSpan timeout, + public AndWhichConstraint, IEnumerable> Push(int numberOfNotifications, TimeSpan timeout, string because = "", params object[] becauseArgs) { IList notifications = new List(); var assertion = Execute.Assertion - .WithExpectation($"Expected {{context}} to push at least {numberOfNotifications} {(numberOfNotifications == 1 ? "notification" : "notifications")}, ") + .WithExpectation($"Expected observable to push at least {numberOfNotifications} {(numberOfNotifications == 1 ? "notification" : "notifications")}, ") .BecauseOf(because, becauseArgs); try @@ -57,23 +59,25 @@ public AndWhichConstraint, IEnumerable> } catch (Exception e) { - assertion.FailWith("but it failed with exception {0}.", e); + if(e is AggregateException aggregateException) + e = aggregateException.InnerException; + assertion.FailWith("but it failed with a {0}.", e); } assertion .ForCondition(notifications.Count >= numberOfNotifications) .FailWith("but {0} were received within {1}.", notifications.Count, timeout); - return new AndWhichConstraint, IEnumerable>(this, notifications); + return new AndWhichConstraint, IEnumerable>(this, notifications); } /// - public async Task, IEnumerable>> PushAsync(int numberOfNotifications, TimeSpan timeout, + public async Task, IEnumerable>> PushAsync(int numberOfNotifications, TimeSpan timeout, string because = "", params object[] becauseArgs) { IList notifications = new List(); var assertion = Execute.Assertion - .WithExpectation($"Expected {{context}} to push at least {numberOfNotifications} {(numberOfNotifications == 1 ? "notification" : "notifications")}, ") + .WithExpectation($"Expected observable to push at least {numberOfNotifications} {(numberOfNotifications == 1 ? "notification" : "notifications")}, ") .BecauseOf(because, becauseArgs); try @@ -89,14 +93,16 @@ public async Task, IEnumerable } catch (Exception e) { - assertion.FailWith("but it failed with exception {0}.", e); + if (e is AggregateException aggregateException) + e = aggregateException.InnerException; + assertion.FailWith("but it failed with a {0}.", e); } assertion .ForCondition(notifications.Count >= numberOfNotifications) .FailWith("but {0} were received within {1}.", notifications.Count, timeout); - return new AndWhichConstraint, IEnumerable>(this, notifications); + return new AndWhichConstraint, IEnumerable>(this, notifications); } /// @@ -106,29 +112,29 @@ public async Task, IEnumerable /// the number of notifications the observer should have recorded by now /// /// - public AndWhichConstraint, IEnumerable> Push(int numberOfNotifications, string because = "", params object[] becauseArgs) + public AndWhichConstraint, IEnumerable> Push(int numberOfNotifications, string because = "", params object[] becauseArgs) => Push(numberOfNotifications, TimeSpan.FromSeconds(10), because, becauseArgs); /// - public Task, IEnumerable>> PushAsync(int numberOfNotifications, string because = "", params object[] becauseArgs) + public Task, IEnumerable>> PushAsync(int numberOfNotifications, string because = "", params object[] becauseArgs) => PushAsync(numberOfNotifications, TimeSpan.FromSeconds(10), because, becauseArgs); /// /// Asserts that at least 1 notification is pushed to the within the next 1 second.
/// This includes any previously recorded notifications since it has been created or cleared. ///
- public AndWhichConstraint, IEnumerable> Push(string because = "", params object[] becauseArgs) + public AndWhichConstraint, IEnumerable> Push(string because = "", params object[] becauseArgs) => Push(1, TimeSpan.FromSeconds(1), because, becauseArgs); /// - public Task, IEnumerable>> PushAsync(string because = "", params object[] becauseArgs) + public Task, IEnumerable>> PushAsync(string because = "", params object[] becauseArgs) => PushAsync(1, TimeSpan.FromSeconds(1), because, becauseArgs); /// /// Asserts that the does not receive any notifications within the specified .
/// This includes any previously recorded notifications since it has been created or cleared. ///
- public AndConstraint> NotPush(TimeSpan timeout, + public AndConstraint> NotPush(TimeSpan timeout, string because = "", params object[] becauseArgs) { bool anyNotifications = Observer.RecordedNotificationStream @@ -141,122 +147,84 @@ public AndConstraint> NotPush(TimeSpan timeout, Execute.Assertion .ForCondition(!anyNotifications) .BecauseOf(because, becauseArgs) - .FailWith("Expected {context} to not push any notifications{reason}, but it did."); + .FailWith("Expected observable to not push any notifications{reason}, but it did."); - return new AndConstraint>(this); + return new AndConstraint>(this); } /// /// Asserts that the does not receive any notifications within the next 100 milliseconds.
/// This includes any previously recorded notifications since it has been created or last cleared. ///
- public AndConstraint> NotPush(string because = "", params object[] becauseArgs) + public AndConstraint> NotPush(string because = "", params object[] becauseArgs) => NotPush(TimeSpan.FromMilliseconds(100), because, becauseArgs); /// /// Asserts that the observed by the fails within the specified . /// - public AndWhichConstraint, Exception> Fail(TimeSpan timeout, - string because = "", params object[] becauseArgs) + public ExceptionAssertions Throw(TimeSpan timeout, string because = "", params object[] becauseArgs) + where TException : Exception { - var exception = Observer.RecordedNotificationStream - .Timeout(timeout) - .Catch(Observable.Empty>>()) - .FirstOrDefaultAsync(recorded => recorded.Value.Kind == NotificationKind.OnError) - .Select(recorded => recorded.Value.Exception) - .ToTask() - .ExecuteInDefaultSynchronizationContext(); - - Execute.Assertion - .ForCondition(exception != null) - .BecauseOf(because, becauseArgs) - .FailWith("Expected {context} to fail within {0}{reason}, but it did not.", timeout); - - return new AndWhichConstraint, Exception>(this, exception); + var notifications = GetRecordedNotifications(timeout).ExecuteInDefaultSynchronizationContext(); + return Throw(notifications, because, becauseArgs); } - /// - public async Task, Exception>> FailAsync(TimeSpan timeout, + /// + public async Task> ThrowAsync(TimeSpan timeout, string because = "", params object[] becauseArgs) + where TException : Exception { - var exception = await Observer.RecordedNotificationStream - .Timeout(timeout) - .Catch(Observable.Empty>>()) - .FirstOrDefaultAsync(recorded => recorded.Value.Kind == NotificationKind.OnError) - .Select(recorded => recorded.Value.Exception) - .ToTask(); - - Execute.Assertion - .ForCondition(exception != null) - .BecauseOf(because, becauseArgs) - .FailWith("Expected {context} to fail within {0}{reason}, but it did not.", timeout); - - return new AndWhichConstraint, Exception>(this, exception); + var notifications = await GetRecordedNotifications(timeout); + return Throw(notifications, because, becauseArgs); } /// /// Asserts that the observed by the fails within the next 1 second. /// - public AndWhichConstraint, Exception> Fail(string because = "", params object[] becauseArgs) - => Fail(TimeSpan.FromSeconds(1), because, becauseArgs); + public ExceptionAssertions Throw(string because = "", params object[] becauseArgs) + where TException : Exception + => Throw(TimeSpan.FromSeconds(1), because, becauseArgs); - /// - public Task, Exception>> FailAsync(string because = "", params object[] becauseArgs) - => FailAsync(TimeSpan.FromSeconds(1), because, becauseArgs); + /// + public Task> ThrowAsync(string because = "", params object[] becauseArgs) + where TException : Exception + => ThrowAsync(TimeSpan.FromSeconds(1), because, becauseArgs); /// /// Asserts that the observed by the completes within the specified . /// - public AndConstraint> Complete(TimeSpan timeout, + public AndConstraint> Complete(TimeSpan timeout, string because = "", params object[] becauseArgs) { - bool completed = Observer.RecordedNotificationStream - .Any(recorded => recorded.Value.Kind == NotificationKind.OnCompleted) - .Timeout(timeout) - .Catch(Observable.Return(false)) - .ToTask() - .ExecuteInDefaultSynchronizationContext(); - - Execute.Assertion - .ForCondition(completed) - .BecauseOf(because, becauseArgs) - .FailWith("Expected {context} to complete within {0}{reason}, but it did not.", timeout); + var notifications = GetRecordedNotifications(timeout).ExecuteInDefaultSynchronizationContext(); - return new AndConstraint>(this); + return Complete(timeout, because, becauseArgs, notifications); } + /// - public async Task>> CompleteAsync(TimeSpan timeout, + public async Task>> CompleteAsync(TimeSpan timeout, string because = "", params object[] becauseArgs) { - bool completed = await Observer.RecordedNotificationStream - .Any(recorded => recorded.Value.Kind == NotificationKind.OnCompleted) - .Timeout(timeout) - .Catch(Observable.Return(false)) - .ToTask(); + var notifications = await GetRecordedNotifications(timeout); - Execute.Assertion - .ForCondition(completed) - .BecauseOf(because, becauseArgs) - .FailWith("Expected {context} to complete within {0}{reason}, but it did not.", timeout); - - return new AndConstraint>(this); + return Complete(timeout, because, becauseArgs, notifications); } /// /// Asserts that the observed by the completes within the next 1 second. /// - public AndConstraint> Complete(string because = "", params object[] becauseArgs) + public AndConstraint> Complete(string because = "", params object[] becauseArgs) => Complete(TimeSpan.FromSeconds(1), because, becauseArgs); /// - public Task>> CompleteAsync(string because = "", params object[] becauseArgs) + public Task>> CompleteAsync(string because = "", params object[] becauseArgs) => CompleteAsync(TimeSpan.FromSeconds(1), because, becauseArgs); /// /// Asserts that the observed by the does not complete within the specified . /// - public AndConstraint> NotComplete(TimeSpan timeout, + public AndConstraint> NotComplete(TimeSpan timeout, string because = "", params object[] becauseArgs) { bool completed = Observer.RecordedNotificationStream @@ -269,15 +237,73 @@ public AndConstraint> NotComplete(TimeSpan timeou Execute.Assertion .ForCondition(!completed) .BecauseOf(because, becauseArgs) - .FailWith("Expected {context} to not complete{reason}, but it did."); + .FailWith("Expected observable to not complete{reason}, but it did."); - return new AndConstraint>(this); + return new AndConstraint>(this); } /// /// Asserts that the observed by the does not complete within the next 100 milliseconds. /// - public AndConstraint> NotComplete(string because = "", params object[] becauseArgs) + public AndConstraint> NotComplete(string because = "", params object[] becauseArgs) => NotComplete(TimeSpan.FromMilliseconds(100), because, becauseArgs); + + protected Task>>> GetRecordedNotifications(TimeSpan timeout) => + Observer.RecordedNotificationStream + .TakeUntil(recorded => recorded.Value.Kind == NotificationKind.OnError) + .TakeUntil(recorded => recorded.Value.Kind == NotificationKind.OnCompleted) + .Timeout(timeout) + .Catch(Observable.Empty>>()) + .ToList() + .ToTask(); + + protected ExceptionAssertions Throw(IList>> notifications, string because, object[] becauseArgs) + where TException : Exception + { + var exception = notifications + .Where(r => r.Value.Kind == NotificationKind.OnError) + .Select(r => r.Value.Exception) + .FirstOrDefault(); + + TException[] expectedExceptions = extractor.OfType(exception).ToArray(); + + Execute.Assertion + .BecauseOf(because, becauseArgs) + .WithExpectation("Expected observable to throw a <{0}>{reason}, ", typeof(TException)) + .ForCondition(exception != null) + .FailWith("but no exception was thrown.") + .Then + .ForCondition(expectedExceptions.Any()) + .FailWith("but found <{0}>: {1}{2}.", + exception?.GetType(), + Environment.NewLine, + exception) + .Then + .ClearExpectation(); + + return new ExceptionAssertions(expectedExceptions); + } + + protected AndConstraint> Complete(TimeSpan timeout, string because, object[] becauseArgs, IList>> notifications) + { + var exception = notifications + .Where(r => r.Value.Kind == NotificationKind.OnError) + .Select(r => r.Value.Exception) + .FirstOrDefault(); + + Execute.Assertion + .WithExpectation("Expected observable to complete within {0}{reason}, ", timeout) + .BecauseOf(because, becauseArgs) + .ForCondition(exception is null) + .FailWith("but it failed with <{0}>: {1}{2}.", + exception?.GetType(), + Environment.NewLine, + exception) + .Then + .ForCondition(notifications.Any(r => r.Value.Kind == NotificationKind.OnCompleted)) + .FailWith("but it did not."); + + return new AndConstraint>(this); + } } } diff --git a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableExtensions.cs b/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ReactiveExtensions.cs similarity index 69% rename from tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableExtensions.cs rename to tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ReactiveExtensions.cs index a9dfce46..8894ab99 100644 --- a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ObservableExtensions.cs +++ b/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ReactiveExtensions.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Linq.Expressions; using System.Reactive; +using System.Reactive.Concurrency; using System.Threading.Tasks; using FluentAssertions; using FluentAssertions.Execution; @@ -10,18 +11,28 @@ namespace GraphQL.Client.Tests.Common.FluentAssertions.Reactive { - public static class ObservableExtensions + public static class ReactiveExtensions { /// /// Create a new subscribed to this /// public static FluentTestObserver Observe(this IObservable observable) => new FluentTestObserver(observable); + /// + /// Create a new subscribed to this + /// + public static FluentTestObserver Observe(this IObservable observable, IScheduler scheduler) => new FluentTestObserver(observable, scheduler); + + /// + /// Create a new subscribed to this + /// + public static FluentTestObserver Observe(this IObservable observable, TestScheduler scheduler) => new FluentTestObserver(observable, scheduler); + /// /// Asserts that the recorded messages contain at lease one item which matches the /// - public static AndWhichConstraint, IEnumerable> WithMessage( - this AndWhichConstraint, IEnumerable> recorderConstraint, Expression> predicate) + public static AndWhichConstraint, IEnumerable> WithMessage( + this AndWhichConstraint, IEnumerable> recorderConstraint, Expression> predicate) { if (predicate is null) throw new ArgumentNullException(nameof(predicate)); @@ -38,8 +49,8 @@ public static AndWhichConstraint, IEnumerable /// Asserts that the last recorded message matches the ///
- public static AndWhichConstraint, IEnumerable> WithLastMessage( - this AndWhichConstraint, IEnumerable> recorderConstraint, Expression> predicate) + public static AndWhichConstraint, IEnumerable> WithLastMessage( + this AndWhichConstraint, IEnumerable> recorderConstraint, Expression> predicate) { if (predicate is null) throw new ArgumentNullException(nameof(predicate)); @@ -57,7 +68,7 @@ public static AndWhichConstraint, IEnumerable public static TPayload GetLastMessage( - this AndWhichConstraint, IEnumerable> + this AndWhichConstraint, IEnumerable> recorderConstraint) => recorderConstraint.Subject.LastOrDefault(); @@ -65,7 +76,7 @@ this AndWhichConstraint, IEnumerable> /// Extracts the last recorded message ///
public static async Task GetLastMessageAsync( - this Task, IEnumerable>> + this Task, IEnumerable>> assertionTask) { var constraint = await assertionTask; @@ -91,7 +102,7 @@ public static TPayload GetLastMessage( /// Clears the recorded notifications on the underlying /// public static void Clear( - this AndWhichConstraint, IEnumerable>>> + this AndWhichConstraint, IEnumerable>>> recorderConstraint) => recorderConstraint.And.Observer.Clear(); } } diff --git a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/RollingReplaySubject.cs b/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/RollingReplaySubject.cs index eef1d294..df779dad 100644 --- a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/RollingReplaySubject.cs +++ b/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/RollingReplaySubject.cs @@ -45,7 +45,7 @@ public void OnCompleted() public IDisposable Subscribe(IObserver observer) => _concatenatedSubjects.Subscribe(observer); - public IEnumerable Snapshot() + public IEnumerable GetSnapshot() { var snapshot = new List(); using (this.Subscribe(item => snapshot.Add(item))) From ccdf82d0a13a75fe13d5b918f817e8b65aeafea4 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Thu, 9 Apr 2020 23:53:47 +0200 Subject: [PATCH 254/455] fix bug in FluentTestObserver --- .../FluentAssertions.Reactive/FluentTestObserver.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/FluentTestObserver.cs b/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/FluentTestObserver.cs index a9350130..5b494159 100644 --- a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/FluentTestObserver.cs +++ b/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/FluentTestObserver.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Reactive; using System.Reactive.Concurrency; @@ -65,7 +66,7 @@ public FluentTestObserver(IObservable subject) { Subject = subject; _observeScheduler = new EventLoopScheduler(); - _subscription = new CompositeDisposable(); subject.ObserveOn(_observeScheduler).Subscribe(this); + _subscription = subject.ObserveOn(_observeScheduler).Subscribe(this); } /// @@ -96,11 +97,8 @@ public FluentTestObserver(IObservable subject, TestScheduler testSched public void Clear() => _rollingReplaySubject.Clear(); /// - public void OnNext(TPayload value) - { - _rollingReplaySubject.OnNext( - new Recorded>(_observeScheduler.Now.UtcTicks, Notification.CreateOnNext(value))); - } + public void OnNext(TPayload value) => + _rollingReplaySubject.OnNext(new Recorded>(_observeScheduler.Now.UtcTicks, Notification.CreateOnNext(value))); /// public void OnError(Exception exception) => From d1950f8d855267fca74746d566cf3f9f994024fc Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Thu, 16 Apr 2020 21:06:50 +0200 Subject: [PATCH 255/455] fix for issue #87 --- src/GraphQL.Client/GraphQLHttpClient.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/GraphQL.Client/GraphQLHttpClient.cs b/src/GraphQL.Client/GraphQLHttpClient.cs index 72b4b907..2a3ebed2 100644 --- a/src/GraphQL.Client/GraphQLHttpClient.cs +++ b/src/GraphQL.Client/GraphQLHttpClient.cs @@ -129,11 +129,9 @@ private async Task> SendHttpPostRequestAsync(bodyStream, cancellationToken); return response.ToGraphQLHttpResponse(httpResponseMessage.Headers, httpResponseMessage.StatusCode); From 94213e53a3aec013e53f1b13216eec6891691124 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Thu, 16 Apr 2020 22:39:46 +0200 Subject: [PATCH 256/455] remove orphaned file --- .../Websocket/IGraphQLWebSocketJsonSerializer.cs | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 src/GraphQL.Client.Http/Websocket/IGraphQLWebSocketJsonSerializer.cs diff --git a/src/GraphQL.Client.Http/Websocket/IGraphQLWebSocketJsonSerializer.cs b/src/GraphQL.Client.Http/Websocket/IGraphQLWebSocketJsonSerializer.cs deleted file mode 100644 index 5f89191d..00000000 --- a/src/GraphQL.Client.Http/Websocket/IGraphQLWebSocketJsonSerializer.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace GraphQL.Client.Http.Websocket { - public interface IGraphQLWebSocketJsonSerializer: IGraphQLJsonSerializer { - GraphQLWebSocketResponse DeserializeWebSocketResponse(byte[] utf8bytes); - } -} From e5b12e732a70f1a893a120c481e99bb9078c134f Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Thu, 16 Apr 2020 23:10:16 +0200 Subject: [PATCH 257/455] include --tags switch on git fetch in github actions --- .github/workflows/branches-ubuntu.yml | 4 ++-- .github/workflows/main-ubuntu.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/branches-ubuntu.yml b/.github/workflows/branches-ubuntu.yml index 1743235f..ae41f4d6 100644 --- a/.github/workflows/branches-ubuntu.yml +++ b/.github/workflows/branches-ubuntu.yml @@ -19,8 +19,8 @@ jobs: fetch-depth: 0 - name: Restore dotnet tools run: dotnet tool restore - - name: Fetch complete repository - run: git fetch + - name: Fetch complete repository including tags + run: git fetch --tags && git describe - name: Generate version info from git history run: dotnet gitversion /output json > gitversion.json - name: Upload version info file diff --git a/.github/workflows/main-ubuntu.yml b/.github/workflows/main-ubuntu.yml index a0d006b7..82707e71 100644 --- a/.github/workflows/main-ubuntu.yml +++ b/.github/workflows/main-ubuntu.yml @@ -23,8 +23,8 @@ jobs: fetch-depth: 0 - name: Restore dotnet tools run: dotnet tool restore - - name: Fetch complete repository - run: git fetch + - name: Fetch complete repository including tags + run: git fetch --tags && git describe - name: Generate version info from git history run: dotnet gitversion /output json > gitversion.json - name: Upload version info file From 32d9e4733192384bc7da1ba237d3718e2aa4aac9 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Thu, 16 Apr 2020 23:16:49 +0200 Subject: [PATCH 258/455] use --tags --force --prune --- .github/workflows/branches-ubuntu.yml | 2 +- .github/workflows/main-ubuntu.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/branches-ubuntu.yml b/.github/workflows/branches-ubuntu.yml index ae41f4d6..72864b9f 100644 --- a/.github/workflows/branches-ubuntu.yml +++ b/.github/workflows/branches-ubuntu.yml @@ -20,7 +20,7 @@ jobs: - name: Restore dotnet tools run: dotnet tool restore - name: Fetch complete repository including tags - run: git fetch --tags && git describe + run: git fetch --tags --force --prune && git describe - name: Generate version info from git history run: dotnet gitversion /output json > gitversion.json - name: Upload version info file diff --git a/.github/workflows/main-ubuntu.yml b/.github/workflows/main-ubuntu.yml index 82707e71..82d24f1e 100644 --- a/.github/workflows/main-ubuntu.yml +++ b/.github/workflows/main-ubuntu.yml @@ -24,7 +24,7 @@ jobs: - name: Restore dotnet tools run: dotnet tool restore - name: Fetch complete repository including tags - run: git fetch --tags && git describe + run: git fetch --tags --force --prune && git describe - name: Generate version info from git history run: dotnet gitversion /output json > gitversion.json - name: Upload version info file From f1c05d7a6b5a6b015be9bedae437ffb3de7dacb2 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Sat, 18 Apr 2020 12:36:51 +0200 Subject: [PATCH 259/455] remove windows workflows, adapt to mainline development --- .github/workflows/branches-windows.yml | 78 --------------- .../{branches-ubuntu.yml => branches.yml} | 5 +- .github/workflows/main-windows.yml | 98 ------------------- .../workflows/{main-ubuntu.yml => master.yml} | 4 +- GitVersion.yml | 2 +- 5 files changed, 6 insertions(+), 181 deletions(-) delete mode 100644 .github/workflows/branches-windows.yml rename .github/workflows/{branches-ubuntu.yml => branches.yml} (97%) delete mode 100644 .github/workflows/main-windows.yml rename .github/workflows/{main-ubuntu.yml => master.yml} (98%) diff --git a/.github/workflows/branches-windows.yml b/.github/workflows/branches-windows.yml deleted file mode 100644 index fdf2b81c..00000000 --- a/.github/workflows/branches-windows.yml +++ /dev/null @@ -1,78 +0,0 @@ -name: Branch workflow -on: - push: - branches-ignore: - - '**' -jobs: - generateVersionInfo: - name: GenerateVersionInfo - runs-on: windows-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - name: Setup dotnet environment - uses: actions/setup-dotnet@master - with: - dotnet-version: '3.1.100' - - name: Restore dotnet tools - run: dotnet tool restore - - name: Fetch complete repository - run: git fetch - - name: Generate version info from git history - run: dotnet dotnet-gitversion /output json | Out-File gitversion.json; Get-Content gitversion.json - - name: Upload version info file - uses: actions/upload-artifact@v1 - with: - name: gitversion - path: gitversion.json - - build: - name: Build - needs: generateVersionInfo - runs-on: windows-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Setup dotnet environment - uses: actions/setup-dotnet@master - with: - dotnet-version: '3.1.100' - - name: Download version info file - uses: actions/download-artifact@v1 - with: - name: gitversion - path: ./ - - name: Inject version info into environment - run: Get-Content gitversion.json | ConvertFrom-Json | ForEach-Object { foreach ($item in $_.PSObject.properties) { "::set-env name=GitVersion_$($item.Name)::$($item.Value)" } }; $env:GitVersion_SemVer - - name: Build solution - run: dotnet build -c Release - - name: Create NuGet packages - run: dotnet pack -c Release --no-build -o nupkg - - name: Upload nuget packages - uses: actions/upload-artifact@v1 - with: - name: nupkg - path: nupkg - - test: - name: Test - needs: [build, generateVersionInfo] - runs-on: windows-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Setup dotnet environment - uses: actions/setup-dotnet@master - with: - dotnet-version: '3.1.100' - - name: Download version info file - uses: actions/download-artifact@v1 - with: - name: gitversion - path: ./ - - name: Inject version info into environment - run: Get-Content gitversion.json | ConvertFrom-Json | ForEach-Object { foreach ($item in $_.PSObject.properties) { "::set-env name=GitVersion_$($item.Name)::$($item.Value)" } } - - name: Run tests - run: dotnet test -c Release -p:ParallelizeTestCollections=false diff --git a/.github/workflows/branches-ubuntu.yml b/.github/workflows/branches.yml similarity index 97% rename from .github/workflows/branches-ubuntu.yml rename to .github/workflows/branches.yml index 72864b9f..8cbec3af 100644 --- a/.github/workflows/branches-ubuntu.yml +++ b/.github/workflows/branches.yml @@ -1,10 +1,11 @@ -name: Branch workflow (Ubuntu) +name: Branch workflow on: push: branches-ignore: - - develop + - master - 'release/**' - 'releases/**' + pull_request: env: DOTNET_CLI_TELEMETRY_OPTOUT: true MSBUILDSINGLELOADCONTEXT: 1 diff --git a/.github/workflows/main-windows.yml b/.github/workflows/main-windows.yml deleted file mode 100644 index 8ab30600..00000000 --- a/.github/workflows/main-windows.yml +++ /dev/null @@ -1,98 +0,0 @@ -name: Main workflow -on: - push: - branches-ignore: - - '**' -jobs: - generateVersionInfo: - name: GenerateVersionInfo - runs-on: windows-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - name: Setup dotnet environment - uses: actions/setup-dotnet@master - with: - dotnet-version: '3.1.100' - - name: Restore dotnet tools - run: dotnet tool restore - - name: Fetch complete repository - run: git fetch - - name: Generate version info from git history - run: dotnet dotnet-gitversion /output json | Out-File gitversion.json; Get-Content gitversion.json - env: - IGNORE_NORMALISATION_GIT_HEAD_MOVE: 1 - - name: Upload version info file - uses: actions/upload-artifact@v1 - with: - name: gitversion - path: gitversion.json - - build: - name: Build - needs: generateVersionInfo - runs-on: windows-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Setup dotnet environment - uses: actions/setup-dotnet@master - with: - dotnet-version: '3.1.100' - - name: Download version info file - uses: actions/download-artifact@v1 - with: - name: gitversion - path: ./ - - name: Inject version info into environment - run: Get-Content gitversion.json | ConvertFrom-Json | ForEach-Object { foreach ($item in $_.PSObject.properties) { "::set-env name=GitVersion_$($item.Name)::$($item.Value)" } }; $env:GitVersion_SemVer - - name: Build solution - run: dotnet build -c Release - - name: Create NuGet packages - run: dotnet pack -c Release --no-build -o nupkg - - name: Upload nuget packages - uses: actions/upload-artifact@v1 - with: - name: nupkg - path: nupkg - - test: - name: Test - needs: [build, generateVersionInfo] - runs-on: windows-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Setup dotnet environment - uses: actions/setup-dotnet@master - with: - dotnet-version: '3.1.100' - - name: Download version info file - uses: actions/download-artifact@v1 - with: - name: gitversion - path: ./ - - name: Inject version info into environment - run: Get-Content gitversion.json | ConvertFrom-Json | ForEach-Object { foreach ($item in $_.PSObject.properties) { "::set-env name=GitVersion_$($item.Name)::$($item.Value)" } } - - name: Run tests - run: dotnet test -c Release -p:ParallelizeTestCollections=false - - publish: - name: Publish - needs: [test] - runs-on: windows-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Download nuget packages - uses: actions/download-artifact@v1 - with: - name: nupkg - - name: Setup Nuget.exe - uses: warrenbuckley/Setup-Nuget@v1 - - name: Configure package source - run: nuget sources Add -Name "GPR" -Source https://nuget.pkg.github.com/graphql-dotnet/index.json -UserName graphql-dotnet -Password ${{secrets.GITHUB_TOKEN}} - - name: push packages - run: nuget push .\nupkg\*.nupkg -SkipDuplicate -Source "GPR" diff --git a/.github/workflows/main-ubuntu.yml b/.github/workflows/master.yml similarity index 98% rename from .github/workflows/main-ubuntu.yml rename to .github/workflows/master.yml index 82d24f1e..ad8b79a5 100644 --- a/.github/workflows/main-ubuntu.yml +++ b/.github/workflows/master.yml @@ -1,8 +1,8 @@ -name: Main workflow (Ubuntu) +name: Master workflow on: push: branches: - - develop + - master - 'release/**' - 'releases/**' tags: diff --git a/GitVersion.yml b/GitVersion.yml index 2210324b..0093d88e 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -1 +1 @@ -mode: ContinuousDeployment +mode: Mainline From 8a45417c421717220d6dcc2c90074b0d3cd75d20 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Sat, 18 Apr 2020 12:47:47 +0200 Subject: [PATCH 260/455] output generatet verions info to build log --- .github/workflows/branches.yml | 2 +- .github/workflows/master.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/branches.yml b/.github/workflows/branches.yml index 8cbec3af..2db62d5a 100644 --- a/.github/workflows/branches.yml +++ b/.github/workflows/branches.yml @@ -23,7 +23,7 @@ jobs: - name: Fetch complete repository including tags run: git fetch --tags --force --prune && git describe - name: Generate version info from git history - run: dotnet gitversion /output json > gitversion.json + run: dotnet gitversion /output json > gitversion.json && cat gitversion.json - name: Upload version info file uses: actions/upload-artifact@v1 with: diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index ad8b79a5..e7e29460 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -26,7 +26,7 @@ jobs: - name: Fetch complete repository including tags run: git fetch --tags --force --prune && git describe - name: Generate version info from git history - run: dotnet gitversion /output json > gitversion.json + run: dotnet gitversion /output json > gitversion.json && cat gitversion.json - name: Upload version info file uses: actions/upload-artifact@v1 with: From c5624303bfec2571ad5e1f2188567fbf0e4899e8 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Sat, 18 Apr 2020 11:22:50 +0200 Subject: [PATCH 261/455] throw custom exception on bad status code --- src/GraphQL.Client/GraphQLHttpClient.cs | 23 +++++++++--- src/GraphQL.Client/GraphQLHttpException.cs | 29 --------------- .../GraphQLHttpRequestException.cs | 37 +++++++++++++++++++ 3 files changed, 54 insertions(+), 35 deletions(-) delete mode 100644 src/GraphQL.Client/GraphQLHttpException.cs create mode 100644 src/GraphQL.Client/GraphQLHttpRequestException.cs diff --git a/src/GraphQL.Client/GraphQLHttpClient.cs b/src/GraphQL.Client/GraphQLHttpClient.cs index 2a3ebed2..53d2a1d9 100644 --- a/src/GraphQL.Client/GraphQLHttpClient.cs +++ b/src/GraphQL.Client/GraphQLHttpClient.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Diagnostics; +using System.IO; using System.Net.Http; using System.Text; using System.Threading; @@ -128,13 +129,23 @@ private async Task> SendHttpPostRequestAsync(bodyStream, cancellationToken); - return response.ToGraphQLHttpResponse(httpResponseMessage.Headers, httpResponseMessage.StatusCode); + var contentStream = await httpResponseMessage.Content.ReadAsStreamAsync(); + + if (httpResponseMessage.IsSuccessStatusCode) + { + var graphQLResponse = await JsonSerializer.DeserializeFromUtf8StreamAsync(contentStream, cancellationToken); + return graphQLResponse.ToGraphQLHttpResponse(httpResponseMessage.Headers, httpResponseMessage.StatusCode); + } + + // error handling + string content = null; + if (contentStream != null) + using (var sr = new StreamReader(contentStream)) + content = await sr.ReadToEndAsync(); + + throw new GraphQLHttpRequestException(httpResponseMessage.StatusCode, httpResponseMessage.Headers, content); } private HttpRequestMessage GenerateHttpRequestMessage(GraphQLRequest request) diff --git a/src/GraphQL.Client/GraphQLHttpException.cs b/src/GraphQL.Client/GraphQLHttpException.cs deleted file mode 100644 index d30bd759..00000000 --- a/src/GraphQL.Client/GraphQLHttpException.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Net.Http; - -namespace GraphQL.Client.Http -{ - - /// - /// An exception thrown on unexpected - /// - public class GraphQLHttpException : Exception - { - - /// - /// The - /// - public HttpResponseMessage HttpResponseMessage { get; } - - /// - /// Creates a new instance of - /// - /// The unexpected - public GraphQLHttpException(HttpResponseMessage httpResponseMessage) : base($"Unexpected {nameof(System.Net.Http.HttpResponseMessage)} with code: {httpResponseMessage?.StatusCode}") - { - HttpResponseMessage = httpResponseMessage ?? throw new ArgumentNullException(nameof(httpResponseMessage)); - } - - } - -} diff --git a/src/GraphQL.Client/GraphQLHttpRequestException.cs b/src/GraphQL.Client/GraphQLHttpRequestException.cs new file mode 100644 index 00000000..c63f8646 --- /dev/null +++ b/src/GraphQL.Client/GraphQLHttpRequestException.cs @@ -0,0 +1,37 @@ +using System; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; + +namespace GraphQL.Client.Http +{ + + /// + /// An exception thrown on unexpected + /// + public class GraphQLHttpRequestException : Exception + { + /// + /// The + /// + public HttpStatusCode StatusCode { get; } + + public HttpResponseHeaders ResponseHeaders { get; } + + public string? Content { get; } + + /// + /// Creates a new instance of + /// + /// + /// + /// + public GraphQLHttpRequestException(HttpStatusCode statusCode, HttpResponseHeaders responseHeaders, string? content) : base($"The HTTP request failed with status code {statusCode}") + { + StatusCode = statusCode; + ResponseHeaders = responseHeaders; + Content = content; + } + } + +} From e7720c39f321eedd378214c32bc271daac601a88 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Sat, 18 Apr 2020 11:49:27 +0200 Subject: [PATCH 262/455] create HttpRequestMessage in virtual member of GraphQLHttpRequest --- src/GraphQL.Client/GraphQLHttpClient.cs | 22 +++++----------- src/GraphQL.Client/GraphQLHttpRequest.cs | 25 +++++++++++++++++++ .../GraphQLHttpRequestException.cs | 8 +++++- 3 files changed, 38 insertions(+), 17 deletions(-) diff --git a/src/GraphQL.Client/GraphQLHttpClient.cs b/src/GraphQL.Client/GraphQLHttpClient.cs index 53d2a1d9..20ba2320 100644 --- a/src/GraphQL.Client/GraphQLHttpClient.cs +++ b/src/GraphQL.Client/GraphQLHttpClient.cs @@ -3,7 +3,6 @@ using System.Diagnostics; using System.IO; using System.Net.Http; -using System.Text; using System.Threading; using System.Threading.Tasks; using GraphQL.Client.Abstractions; @@ -128,7 +127,11 @@ public IObservable> CreateSubscriptionStream> SendHttpPostRequestAsync(GraphQLRequest request, CancellationToken cancellationToken = default) { var preprocessedRequest = await Options.PreprocessRequest(request, this); - using var httpRequestMessage = GenerateHttpRequestMessage(preprocessedRequest); + + if(!(preprocessedRequest is GraphQLHttpRequest graphQLHttpRequest)) + graphQLHttpRequest = new GraphQLHttpRequest(preprocessedRequest); + + using var httpRequestMessage = graphQLHttpRequest.ToHttpRequestMessage(Options, JsonSerializer); using var httpResponseMessage = await HttpClient.SendAsync(httpRequestMessage, HttpCompletionOption.ResponseHeadersRead, cancellationToken); var contentStream = await httpResponseMessage.Content.ReadAsStreamAsync(); @@ -148,22 +151,9 @@ private async Task> SendHttpPostRequestAsync /// Allows to preprocess a before it is sent, i.e. add custom headers /// [IgnoreDataMember] + [Obsolete("Inherit from GraphQLHttpRequest and override ToHttpRequestMessage() to customize the HttpRequestMessage. Will be removed in v4.0.0.")] public Action PreprocessHttpRequestMessage { get; set; } = message => { }; + + /// + /// Creates a from this . + /// Used by to convert GraphQL requests when sending them as regular HTTP requests. + /// + /// the passed from + /// the passed from + /// + public virtual HttpRequestMessage ToHttpRequestMessage(GraphQLHttpClientOptions options, IGraphQLJsonSerializer serializer) + { + var message = new HttpRequestMessage(HttpMethod.Post, options.EndPoint) + { + Content = new StringContent(serializer.SerializeToString(this), Encoding.UTF8, options.MediaType) + }; + + PreprocessHttpRequestMessage(message); + return message; + } } } diff --git a/src/GraphQL.Client/GraphQLHttpRequestException.cs b/src/GraphQL.Client/GraphQLHttpRequestException.cs index c63f8646..3b8230ad 100644 --- a/src/GraphQL.Client/GraphQLHttpRequestException.cs +++ b/src/GraphQL.Client/GraphQLHttpRequestException.cs @@ -12,12 +12,18 @@ namespace GraphQL.Client.Http public class GraphQLHttpRequestException : Exception { /// - /// The + /// The returned status code /// public HttpStatusCode StatusCode { get; } + /// + /// the returned response headers + /// public HttpResponseHeaders ResponseHeaders { get; } + /// + /// the returned content + /// public string? Content { get; } /// From 086694c433b66e620375a26d8c314a7b9b3debe2 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Sat, 18 Apr 2020 11:59:59 +0200 Subject: [PATCH 263/455] move conversion to GraphQLHttpRequest into Options.PreprocessRequest --- src/GraphQL.Client/GraphQLHttpClient.cs | 7 ++----- src/GraphQL.Client/GraphQLHttpClientOptions.cs | 3 ++- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/GraphQL.Client/GraphQLHttpClient.cs b/src/GraphQL.Client/GraphQLHttpClient.cs index 20ba2320..b2e70ed2 100644 --- a/src/GraphQL.Client/GraphQLHttpClient.cs +++ b/src/GraphQL.Client/GraphQLHttpClient.cs @@ -127,11 +127,8 @@ public IObservable> CreateSubscriptionStream> SendHttpPostRequestAsync(GraphQLRequest request, CancellationToken cancellationToken = default) { var preprocessedRequest = await Options.PreprocessRequest(request, this); - - if(!(preprocessedRequest is GraphQLHttpRequest graphQLHttpRequest)) - graphQLHttpRequest = new GraphQLHttpRequest(preprocessedRequest); - - using var httpRequestMessage = graphQLHttpRequest.ToHttpRequestMessage(Options, JsonSerializer); + + using var httpRequestMessage = preprocessedRequest.ToHttpRequestMessage(Options, JsonSerializer); using var httpResponseMessage = await HttpClient.SendAsync(httpRequestMessage, HttpCompletionOption.ResponseHeadersRead, cancellationToken); var contentStream = await httpResponseMessage.Content.ReadAsStreamAsync(); diff --git a/src/GraphQL.Client/GraphQLHttpClientOptions.cs b/src/GraphQL.Client/GraphQLHttpClientOptions.cs index 6b93cb7b..8b551cf0 100644 --- a/src/GraphQL.Client/GraphQLHttpClientOptions.cs +++ b/src/GraphQL.Client/GraphQLHttpClientOptions.cs @@ -45,7 +45,8 @@ public class GraphQLHttpClientOptions /// /// Request preprocessing function. Can be used i.e. to inject authorization info into a GraphQL request payload. /// - public Func> PreprocessRequest { get; set; } = (request, client) => Task.FromResult(request); + public Func> PreprocessRequest { get; set; } = (request, client) => + Task.FromResult(request is GraphQLHttpRequest graphQLHttpRequest ? graphQLHttpRequest : new GraphQLHttpRequest(request)); /// /// This callback is called after successfully establishing a websocket connection but before any regular request is made. From f7cda9bb4b12844c0ec80738effba3d2566268e4 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Sat, 18 Apr 2020 12:12:46 +0200 Subject: [PATCH 264/455] remove unused using directive --- src/GraphQL.Client/GraphQLHttpRequestException.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/GraphQL.Client/GraphQLHttpRequestException.cs b/src/GraphQL.Client/GraphQLHttpRequestException.cs index 3b8230ad..2071da14 100644 --- a/src/GraphQL.Client/GraphQLHttpRequestException.cs +++ b/src/GraphQL.Client/GraphQLHttpRequestException.cs @@ -1,6 +1,5 @@ using System; using System.Net; -using System.Net.Http; using System.Net.Http.Headers; namespace GraphQL.Client.Http From 7d06d2ddaccec0fb617b1e4fc49e31d18452a2fc Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Sun, 19 Apr 2020 00:16:33 +0200 Subject: [PATCH 265/455] Update src/GraphQL.Client/GraphQLHttpRequestException.cs Co-Authored-By: Ivan Maximov --- src/GraphQL.Client/GraphQLHttpRequestException.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/GraphQL.Client/GraphQLHttpRequestException.cs b/src/GraphQL.Client/GraphQLHttpRequestException.cs index 2071da14..5b3a8b9f 100644 --- a/src/GraphQL.Client/GraphQLHttpRequestException.cs +++ b/src/GraphQL.Client/GraphQLHttpRequestException.cs @@ -38,5 +38,4 @@ public GraphQLHttpRequestException(HttpStatusCode statusCode, HttpResponseHeader Content = content; } } - } From 2ecf6830cbd38752c6315f50df472386604005bf Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Sun, 19 Apr 2020 00:20:31 +0200 Subject: [PATCH 266/455] rename method --- src/GraphQL.Client/GraphQLHttpClient.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/GraphQL.Client/GraphQLHttpClient.cs b/src/GraphQL.Client/GraphQLHttpClient.cs index b2e70ed2..1777e0b5 100644 --- a/src/GraphQL.Client/GraphQLHttpClient.cs +++ b/src/GraphQL.Client/GraphQLHttpClient.cs @@ -73,7 +73,7 @@ public async Task> SendQueryAsync(GraphQLR if (Options.UseWebSocketForQueriesAndMutations) return await _graphQlHttpWebSocket.SendRequest(request, cancellationToken); - return await SendHttpPostRequestAsync(request, cancellationToken); + return await SendHttpRequestAsync(request, cancellationToken); } /// @@ -124,7 +124,7 @@ public IObservable> CreateSubscriptionStream> SendHttpPostRequestAsync(GraphQLRequest request, CancellationToken cancellationToken = default) + private async Task> SendHttpRequestAsync(GraphQLRequest request, CancellationToken cancellationToken = default) { var preprocessedRequest = await Options.PreprocessRequest(request, this); From 70305e3c14c3eafa68473c89e71001560b93b90a Mon Sep 17 00:00:00 2001 From: Paul Klein Date: Sun, 19 Apr 2020 13:59:35 +1200 Subject: [PATCH 267/455] Fix Map array deserialisation in Newtonsoft serialiser Resolves graphql-dotnet/graphql-client#28 --- src/GraphQL.Client.Serializer.Newtonsoft/MapConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GraphQL.Client.Serializer.Newtonsoft/MapConverter.cs b/src/GraphQL.Client.Serializer.Newtonsoft/MapConverter.cs index f15787e8..dba2a348 100644 --- a/src/GraphQL.Client.Serializer.Newtonsoft/MapConverter.cs +++ b/src/GraphQL.Client.Serializer.Newtonsoft/MapConverter.cs @@ -53,7 +53,7 @@ private TDictionary ReadDictionary(JToken element) where TDictionar private IEnumerable ReadArray(JToken element) { - foreach (var item in element.Values()) + foreach (var item in (JArray)element) { if (IsUnsupportedJTokenType(item.Type)) continue; From 1edeae3dfac5fc59d795c6059e59bfd718d1a9ed Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Sun, 19 Apr 2020 21:36:36 +0200 Subject: [PATCH 268/455] Update src/GraphQL.Client.Serializer.Newtonsoft/MapConverter.cs --- src/GraphQL.Client.Serializer.Newtonsoft/MapConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GraphQL.Client.Serializer.Newtonsoft/MapConverter.cs b/src/GraphQL.Client.Serializer.Newtonsoft/MapConverter.cs index dba2a348..a8c13ff7 100644 --- a/src/GraphQL.Client.Serializer.Newtonsoft/MapConverter.cs +++ b/src/GraphQL.Client.Serializer.Newtonsoft/MapConverter.cs @@ -51,7 +51,7 @@ private TDictionary ReadDictionary(JToken element) where TDictionar return result; } - private IEnumerable ReadArray(JToken element) + private IEnumerable ReadArray(JArray element) { foreach (var item in (JArray)element) { From bc098b55dfc1f6e2e6ed49960846b92f989ef3c3 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Sun, 19 Apr 2020 21:36:45 +0200 Subject: [PATCH 269/455] Update src/GraphQL.Client.Serializer.Newtonsoft/MapConverter.cs --- src/GraphQL.Client.Serializer.Newtonsoft/MapConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GraphQL.Client.Serializer.Newtonsoft/MapConverter.cs b/src/GraphQL.Client.Serializer.Newtonsoft/MapConverter.cs index a8c13ff7..c2739b41 100644 --- a/src/GraphQL.Client.Serializer.Newtonsoft/MapConverter.cs +++ b/src/GraphQL.Client.Serializer.Newtonsoft/MapConverter.cs @@ -53,7 +53,7 @@ private TDictionary ReadDictionary(JToken element) where TDictionar private IEnumerable ReadArray(JArray element) { - foreach (var item in (JArray)element) + foreach (var item in element) { if (IsUnsupportedJTokenType(item.Type)) continue; From 29407e0d5b38f315333644d187d103cabed01822 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Sun, 19 Apr 2020 21:53:29 +0200 Subject: [PATCH 270/455] change versioning mode to continuous deployment --- GitVersion.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/GitVersion.yml b/GitVersion.yml index 0093d88e..030bf738 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -1 +1,4 @@ -mode: Mainline +mode: ContinuousDeployment +branches: + master: + tag: alpha From 39908f08c457684d31188475b047385c4cca3eaf Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Mon, 20 Apr 2020 21:59:10 +0200 Subject: [PATCH 271/455] add action property to configure ClientWebsocketOptions to GraphQLHttpClientOptions --- src/GraphQL.Client/GraphQLHttpClientOptions.cs | 6 ++++++ src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs | 8 +++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/GraphQL.Client/GraphQLHttpClientOptions.cs b/src/GraphQL.Client/GraphQLHttpClientOptions.cs index 8b551cf0..0c6df2ee 100644 --- a/src/GraphQL.Client/GraphQLHttpClientOptions.cs +++ b/src/GraphQL.Client/GraphQLHttpClientOptions.cs @@ -1,6 +1,7 @@ using System; using System.Net.Http; using System.Net.Http.Headers; +using System.Net.WebSockets; using System.Threading.Tasks; namespace GraphQL.Client.Http @@ -52,5 +53,10 @@ public class GraphQLHttpClientOptions /// This callback is called after successfully establishing a websocket connection but before any regular request is made. /// public Func OnWebsocketConnected { get; set; } = client => Task.CompletedTask; + + /// + /// Configure additional websocket options (i.e. headers). This will not be invoked on Windows 7 when targeting .NET Framework 4.x. + /// + public Action ConfigureWebsocketOptions { get; set; } = options => { }; } } diff --git a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs index f0156030..227b53cd 100644 --- a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs +++ b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs @@ -399,12 +399,13 @@ public Task InitializeWebSocket() nativeWebSocket.Options.AddSubProtocol("graphql-ws"); nativeWebSocket.Options.ClientCertificates = ((HttpClientHandler)Options.HttpMessageHandler).ClientCertificates; nativeWebSocket.Options.UseDefaultCredentials = ((HttpClientHandler)Options.HttpMessageHandler).UseDefaultCredentials; - break; + Options.ConfigureWebsocketOptions(nativeWebSocket.Options); + break; case System.Net.WebSockets.Managed.ClientWebSocket managedWebSocket: managedWebSocket.Options.AddSubProtocol("graphql-ws"); managedWebSocket.Options.ClientCertificates = ((HttpClientHandler)Options.HttpMessageHandler).ClientCertificates; managedWebSocket.Options.UseDefaultCredentials = ((HttpClientHandler)Options.HttpMessageHandler).UseDefaultCredentials; - break; + break; default: throw new NotSupportedException($"unknown websocket type {_clientWebSocket.GetType().Name}"); } @@ -413,6 +414,7 @@ public Task InitializeWebSocket() _clientWebSocket.Options.AddSubProtocol("graphql-ws"); _clientWebSocket.Options.ClientCertificates = ((HttpClientHandler)Options.HttpMessageHandler).ClientCertificates; _clientWebSocket.Options.UseDefaultCredentials = ((HttpClientHandler)Options.HttpMessageHandler).UseDefaultCredentials; + Options.ConfigureWebsocketOptions(_clientWebSocket.Options); #endif return _initializeWebSocketTask = ConnectAsync(_internalCancellationToken); } @@ -609,7 +611,7 @@ public void Complete() /// Task to await the completion (a.k.a. disposal) of this websocket. /// /// Async disposal as recommended by Stephen Cleary (https://blog.stephencleary.com/2013/03/async-oop-6-disposal.html) - public Task Completion { get; private set; } + public Task? Completion { get; private set; } private readonly object _completedLocker = new object(); private async Task CompleteAsync() From cbe68e18a6d21ac6a018f7f57424f235d4b15fe5 Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Tue, 21 Apr 2020 01:10:33 +0300 Subject: [PATCH 272/455] fix build --- src/GraphQL.Client/GraphQLHttpRequest.cs | 2 ++ tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/GraphQL.Client/GraphQLHttpRequest.cs b/src/GraphQL.Client/GraphQLHttpRequest.cs index abf5d0d3..00f90d4a 100644 --- a/src/GraphQL.Client/GraphQLHttpRequest.cs +++ b/src/GraphQL.Client/GraphQLHttpRequest.cs @@ -41,7 +41,9 @@ public virtual HttpRequestMessage ToHttpRequestMessage(GraphQLHttpClientOptions Content = new StringContent(serializer.SerializeToString(this), Encoding.UTF8, options.MediaType) }; +#pragma warning disable CS0618 // Type or member is obsolete PreprocessHttpRequestMessage(message); +#pragma warning restore CS0618 // Type or member is obsolete return message; } } diff --git a/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs b/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs index 4e9cd45a..5855e626 100644 --- a/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs +++ b/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs @@ -173,7 +173,9 @@ public async void PreprocessHttpRequestMessageIsCalled() var callbackTester = new CallbackMonitor(); var graphQLRequest = new GraphQLHttpRequest($"{{ human(id: \"1\") {{ name }} }}") { +#pragma warning disable CS0618 // Type or member is obsolete PreprocessHttpRequestMessage = callbackTester.Invoke +#pragma warning restore CS0618 // Type or member is obsolete }; var defaultHeaders = StarWarsClient.HttpClient.DefaultRequestHeaders; From cc131e23e18b6e93ca49b21d34cd3e2162e60f98 Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Tue, 21 Apr 2020 01:28:04 +0300 Subject: [PATCH 273/455] revert itemgroup --- src/GraphQL.Client/GraphQL.Client.csproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/GraphQL.Client/GraphQL.Client.csproj b/src/GraphQL.Client/GraphQL.Client.csproj index f6f7c0fe..febf2911 100644 --- a/src/GraphQL.Client/GraphQL.Client.csproj +++ b/src/GraphQL.Client/GraphQL.Client.csproj @@ -15,6 +15,10 @@ NETFRAMEWORK + + + + From 7004c126f1c20276e57dda3633aebef84dde7a35 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Tue, 21 Apr 2020 22:31:15 +0200 Subject: [PATCH 274/455] Return a list for a JSON array --- .../MapConverter.cs | 8 ++++---- .../MapConverter.cs | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/GraphQL.Client.Serializer.Newtonsoft/MapConverter.cs b/src/GraphQL.Client.Serializer.Newtonsoft/MapConverter.cs index c2739b41..10a687f4 100644 --- a/src/GraphQL.Client.Serializer.Newtonsoft/MapConverter.cs +++ b/src/GraphQL.Client.Serializer.Newtonsoft/MapConverter.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using System.Runtime.InteropServices; +using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -24,11 +24,11 @@ public override Map ReadJson(JsonReader reader, Type objectType, Map existingVal throw new ArgumentException("This converter can only parse when the root element is a JSON Object."); } - private object ReadToken(JToken? token) => + private object? ReadToken(JToken? token) => token switch { JObject jObject => ReadDictionary>(jObject), - JArray jArray => ReadArray(jArray), + JArray jArray => ReadArray(jArray).ToList(), JValue jValue => jValue.Value, JConstructor _ => throw new ArgumentOutOfRangeException(nameof(token.Type), "cannot deserialize a JSON constructor"), @@ -51,7 +51,7 @@ private TDictionary ReadDictionary(JToken element) where TDictionar return result; } - private IEnumerable ReadArray(JArray element) + private IEnumerable ReadArray(JArray element) { foreach (var item in element) { diff --git a/src/GraphQL.Client.Serializer.SystemTextJson/MapConverter.cs b/src/GraphQL.Client.Serializer.SystemTextJson/MapConverter.cs index bbaa2fe9..63d3d9fc 100644 --- a/src/GraphQL.Client.Serializer.SystemTextJson/MapConverter.cs +++ b/src/GraphQL.Client.Serializer.SystemTextJson/MapConverter.cs @@ -41,7 +41,7 @@ private TDictionary ReadDictionary(JsonElement element) where TDict return result; } - private IEnumerable ReadArray(JsonElement value) + private IEnumerable ReadArray(JsonElement value) { foreach (var item in value.EnumerateArray()) { @@ -49,7 +49,7 @@ private IEnumerable ReadArray(JsonElement value) } } - private object ReadValue(JsonElement value) + private object? ReadValue(JsonElement value) => value.ValueKind switch { JsonValueKind.Array => ReadArray(value).ToList(), @@ -65,15 +65,15 @@ private object ReadValue(JsonElement value) private object ReadNumber(JsonElement value) { - if (value.TryGetInt32(out var i)) + if (value.TryGetInt32(out int i)) return i; - else if (value.TryGetInt64(out var l)) + else if (value.TryGetInt64(out long l)) return l; else if (BigInteger.TryParse(value.GetRawText(), out var bi)) return bi; - else if (value.TryGetDouble(out var d)) + else if (value.TryGetDouble(out double d)) return d; - else if (value.TryGetDecimal(out var dd)) + else if (value.TryGetDecimal(out decimal dd)) return dd; throw new NotImplementedException($"Unexpected Number value. Raw text was: {value.GetRawText()}"); From 451791baedecb1ceea8c3a33b36c3edabb96bdc2 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Tue, 21 Apr 2020 23:04:02 +0200 Subject: [PATCH 275/455] create test for consistency between MapConverters --- .../ConsistencyTests.cs | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 tests/GraphQL.Client.Serializer.Tests/ConsistencyTests.cs diff --git a/tests/GraphQL.Client.Serializer.Tests/ConsistencyTests.cs b/tests/GraphQL.Client.Serializer.Tests/ConsistencyTests.cs new file mode 100644 index 00000000..2fe14e7c --- /dev/null +++ b/tests/GraphQL.Client.Serializer.Tests/ConsistencyTests.cs @@ -0,0 +1,62 @@ +using System.Collections.Generic; +using FluentAssertions; +using FluentAssertions.Execution; +using GraphQL.Client.Serializer.Newtonsoft; +using GraphQL.Client.Serializer.SystemTextJson; +using Newtonsoft.Json; +using Xunit; + +namespace GraphQL.Client.Serializer.Tests +{ + public class ConsistencyTests + { + [Fact] + public void MapConvertersShouldBehaveConsistent() + { + const string json = @"{ + ""array"": [ + ""some stuff"", + ""something else"" + ], + ""string"": ""this is a string"", + ""boolean"": true, + ""number"": 1234.567, + ""nested object"": { + ""prop1"": false + }, + ""arrayOfObjects"": [ + {""number"": 1234.567}, + {""number"": 567.8} + ] + }"; + + var newtonsoftSerializer = new NewtonsoftJsonSerializer(); + var systemTextJsonSerializer = new SystemTextJsonSerializer(); + + var newtonsoftMap = JsonConvert.DeserializeObject(json, newtonsoftSerializer.JsonSerializerSettings); + var systemTextJsonMap = System.Text.Json.JsonSerializer.Deserialize(json, systemTextJsonSerializer.Options); + + + using(new AssertionScope()) + { + CompareMaps(newtonsoftMap, systemTextJsonMap); + } + + newtonsoftMap.Should().BeEquivalentTo(systemTextJsonMap, options => options + .RespectingRuntimeTypes()); + } + + private void CompareMaps(Dictionary first, Dictionary second) + { + foreach (var keyValuePair in first) + { + second.Should().ContainKey(keyValuePair.Key); + second[keyValuePair.Key].Should().BeOfType(keyValuePair.Value.GetType()); + if(keyValuePair.Value is Dictionary map) + CompareMaps(map, (Dictionary)second[keyValuePair.Key]); + else + keyValuePair.Value.Should().BeEquivalentTo(second[keyValuePair.Key]); + } + } + } +} From 1e4a7cbb1083ff42373d0a521c48354d7414433f Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Tue, 21 Apr 2020 23:18:48 +0200 Subject: [PATCH 276/455] add non-generic interface and implement it with GraphQLResponse --- src/GraphQL.Primitives/GraphQLResponse.cs | 3 ++- src/GraphQL.Primitives/IGraphQLResponse.cs | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 src/GraphQL.Primitives/IGraphQLResponse.cs diff --git a/src/GraphQL.Primitives/GraphQLResponse.cs b/src/GraphQL.Primitives/GraphQLResponse.cs index 0b2781fd..24d3a4aa 100644 --- a/src/GraphQL.Primitives/GraphQLResponse.cs +++ b/src/GraphQL.Primitives/GraphQLResponse.cs @@ -6,11 +6,12 @@ namespace GraphQL { - public class GraphQLResponse : IEquatable?> + public class GraphQLResponse : IGraphQLResponse, IEquatable?> { [DataMember(Name = "data")] public T Data { get; set; } + object IGraphQLResponse.Data => Data; [DataMember(Name = "errors")] public GraphQLError[]? Errors { get; set; } diff --git a/src/GraphQL.Primitives/IGraphQLResponse.cs b/src/GraphQL.Primitives/IGraphQLResponse.cs new file mode 100644 index 00000000..789d686f --- /dev/null +++ b/src/GraphQL.Primitives/IGraphQLResponse.cs @@ -0,0 +1,11 @@ +namespace GraphQL +{ + public interface IGraphQLResponse + { + object Data { get; } + + GraphQLError[]? Errors { get; set; } + + Map? Extensions { get; set; } + } +} From 26e4dc83e20d81dcbdb30c2c620e321682e0264b Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Tue, 21 Apr 2020 23:19:29 +0200 Subject: [PATCH 277/455] make some resharper-suggested fixes --- .../GraphQLRequestTest.cs | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/tests/GraphQL.Primitives.Tests/GraphQLRequestTest.cs b/tests/GraphQL.Primitives.Tests/GraphQLRequestTest.cs index 8fbda085..145ecfa6 100644 --- a/tests/GraphQL.Primitives.Tests/GraphQLRequestTest.cs +++ b/tests/GraphQL.Primitives.Tests/GraphQLRequestTest.cs @@ -75,8 +75,8 @@ public void InEquality1Fact() [Fact] public void InEquality2Fact() { - GraphQLRequest graphQLRequest1 = new GraphQLRequest("{hero{name}}", new { varName = "varValue1" }, "operationName"); - GraphQLRequest graphQLRequest2 = new GraphQLRequest("{hero{name}}", new { varName = "varValue2" }, "operationName"); + var graphQLRequest1 = new GraphQLRequest("{hero{name}}", new { varName = "varValue1" }, "operationName"); + var graphQLRequest2 = new GraphQLRequest("{hero{name}}", new { varName = "varValue2" }, "operationName"); Assert.NotEqual(graphQLRequest1, graphQLRequest2); } @@ -122,8 +122,11 @@ public void PropertyQueryGetFact() [Fact] public void PropertyQuerySetFact() { - var graphQLRequest = new GraphQLRequest("{hero{name}}", new { varName = "varValue1" }, "operationName"); - graphQLRequest.Query = "{hero{name2}}"; + var graphQLRequest = + new GraphQLRequest("{hero{name}}", new {varName = "varValue1"}, "operationName") + { + Query = "{hero{name2}}" + }; Assert.Equal("{hero{name2}}", graphQLRequest.Query); } @@ -144,8 +147,10 @@ public void PropertyOperationNameNullGetFact() [Fact] public void PropertyOperationNameSetFact() { - var graphQLRequest = new GraphQLRequest("{hero{name}}", new { varName = "varValue" }, "operationName1"); - graphQLRequest.OperationName = "operationName2"; + var graphQLRequest = new GraphQLRequest("{hero{name}}", new {varName = "varValue"}, "operationName1") + { + OperationName = "operationName2" + }; Assert.Equal("operationName2", graphQLRequest.OperationName); } @@ -166,10 +171,9 @@ public void PropertyVariableNullGetFact() [Fact] public void PropertyVariableSetFact() { - var graphQLRequest = new GraphQLRequest("{hero{name}}", new { varName = "varValue1" }, "operationName1"); - graphQLRequest.Variables = new + var graphQLRequest = new GraphQLRequest("{hero{name}}", new {varName = "varValue1"}, "operationName1") { - varName = "varValue2" + Variables = new {varName = "varValue2"} }; Assert.Equal(new { From c58dc9ea0cd1ebb1a340c2a0e552d74a5b06a3d8 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Tue, 21 Apr 2020 23:39:24 +0200 Subject: [PATCH 278/455] fix workflow paths in sln --- GraphQL.Client.sln | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/GraphQL.Client.sln b/GraphQL.Client.sln index 24ea23a4..ab95f2ec 100644 --- a/GraphQL.Client.sln +++ b/GraphQL.Client.sln @@ -33,9 +33,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphQL.Server.Test", "test EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{05CAF9B2-981E-40C0-AE31-5FA56E351F12}" ProjectSection(SolutionItems) = preProject - .github\workflows\branches-ubuntu.yml = .github\workflows\branches-ubuntu.yml .github\workflows\branches.yml = .github\workflows\branches.yml - .github\workflows\main.yml = .github\workflows\main.yml + .github\workflows\master.yml = .github\workflows\master.yml EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphQL.Primitives", "src\GraphQL.Primitives\GraphQL.Primitives.csproj", "{87FC440E-6A4D-47D8-9EB2-416FC31CC4A6}" @@ -64,7 +63,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphQL.Client.Serializer.S EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{89AD33AB-64F6-4F82-822F-21DF7A10CEC0}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GraphQL.Client.Example", "examples\GraphQL.Client.Example\GraphQL.Client.Example.csproj", "{6B13B87D-1EF4-485F-BC5D-891E2F4DA6CD}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphQL.Client.Example", "examples\GraphQL.Client.Example\GraphQL.Client.Example.csproj", "{6B13B87D-1EF4-485F-BC5D-891E2F4DA6CD}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution From 2668f816655e84accc3c169e03edec97c0bb9e2b Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Wed, 22 Apr 2020 00:04:02 +0200 Subject: [PATCH 279/455] Fix #207 --- src/GraphQL.Client/GraphQLHttpClientExtensions.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/GraphQL.Client/GraphQLHttpClientExtensions.cs b/src/GraphQL.Client/GraphQLHttpClientExtensions.cs index 31f29785..71ffe5f6 100644 --- a/src/GraphQL.Client/GraphQLHttpClientExtensions.cs +++ b/src/GraphQL.Client/GraphQLHttpClientExtensions.cs @@ -27,11 +27,20 @@ public static IObservable> CreateSubscriptionStream public static IObservable> CreateSubscriptionStream( this IGraphQLClient client, GraphQLRequest request, Func defineResponseType, Action webSocketExceptionHandler) { _ = defineResponseType; return client.CreateSubscriptionStream(request, webSocketExceptionHandler); } + + /// + public static IObservable> CreateSubscriptionStream( + this IGraphQLClient client, GraphQLRequest request, Func defineResponseType) + { + _ = defineResponseType; + return client.CreateSubscriptionStream(request); + } } } From 559db232b61a6335350669f00bdc2fba233edfb3 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Wed, 22 Apr 2020 00:05:52 +0200 Subject: [PATCH 280/455] cleanup --- src/GraphQL.Client/GraphQLHttpClientExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/GraphQL.Client/GraphQLHttpClientExtensions.cs b/src/GraphQL.Client/GraphQLHttpClientExtensions.cs index 71ffe5f6..041ed5c6 100644 --- a/src/GraphQL.Client/GraphQLHttpClientExtensions.cs +++ b/src/GraphQL.Client/GraphQLHttpClientExtensions.cs @@ -27,7 +27,7 @@ public static IObservable> CreateSubscriptionStream + /// public static IObservable> CreateSubscriptionStream( this IGraphQLClient client, GraphQLRequest request, Func defineResponseType, Action webSocketExceptionHandler) { @@ -35,7 +35,7 @@ public static IObservable> CreateSubscriptionStream(request, webSocketExceptionHandler); } - /// + /// public static IObservable> CreateSubscriptionStream( this IGraphQLClient client, GraphQLRequest request, Func defineResponseType) { From 171657a39d7d11184c3378ae9b228daf8f71bec5 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Wed, 22 Apr 2020 00:09:06 +0200 Subject: [PATCH 281/455] fix #210 --- src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs index 901fd919..643df77e 100644 --- a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs +++ b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs @@ -614,7 +614,8 @@ public void Complete() private readonly object _completedLocker = new object(); private async Task CompleteAsync() { - Debug.WriteLine($"disposing websocket {_clientWebSocket.GetHashCode()}..."); + Debug.WriteLine("disposing GraphQLHttpWebSocket..."); + _incomingMessagesConnection?.Dispose(); if (!_internalCancellationTokenSource.IsCancellationRequested) @@ -634,7 +635,7 @@ private async Task CompleteAsync() _sendLoopScheduler?.Dispose(); _receiveLoopScheduler?.Dispose(); - Debug.WriteLine($"websocket {_clientWebSocket.GetHashCode()} disposed"); + Debug.WriteLine("GraphQLHttpWebSocket disposed"); } #endregion From 561b1d75a64149952c68951fcdf699a5ae4aa01a Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Wed, 22 Apr 2020 00:48:35 +0200 Subject: [PATCH 282/455] prevent GraphQL.Client.Tests.Common from creating a nuget package --- .../GraphQL.Client.Tests.Common.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj index c1806363..6aeff4a3 100644 --- a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj +++ b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj @@ -2,6 +2,7 @@ netstandard2.0 + false From f2baba077f916850083db917237c1673d446b5c8 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Wed, 6 May 2020 16:12:14 +0200 Subject: [PATCH 283/455] fix example --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 60fd2bd1..86826eba 100644 --- a/README.md +++ b/README.md @@ -54,18 +54,18 @@ Be careful when using `byte[]` in your variables object, as most JSON serializer var graphQLClient = new GraphQLHttpClient("https://swapi.apis.guru/", new NewtonsoftJsonSerializer()); public class PersonAndFilmsResponse { - public PersonContent Person { get; set; } + public Person Person { get; set; } +} - public class PersonContent { - public string Name { get; set; } - public FilmConnectionContent FilmConnection { get; set; } +public class Person { + public string Name { get; set; } + public FilmConnectionContent FilmConnection { get; set; } - public class FilmConnectionContent { - public List Films { get; set; } + public class FilmConnectionContent { + public List Films { get; set; } - public class FilmContent { - public string Title { get; set; } - } + public class FilmContent { + public string Title { get; set; } } } } From 64c1d6fd39a9dde7a134b63150ece0f7f864c54c Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Wed, 6 May 2020 16:15:54 +0200 Subject: [PATCH 284/455] clarify example in readme readme --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 86826eba..08fc6cad 100644 --- a/README.md +++ b/README.md @@ -51,8 +51,6 @@ Be careful when using `byte[]` in your variables object, as most JSON serializer ### Execute Query/Mutation: ```csharp -var graphQLClient = new GraphQLHttpClient("https://swapi.apis.guru/", new NewtonsoftJsonSerializer()); - public class PersonAndFilmsResponse { public Person Person { get; set; } } @@ -70,11 +68,18 @@ public class Person { } } +var graphQLClient = new GraphQLHttpClient("https://swapi.apis.guru/", new NewtonsoftJsonSerializer()); var graphQLResponse = await graphQLClient.SendQueryAsync(personAndFilmsRequest); var personName = graphQLResponse.Data.Person.Name; ``` +Using the extension method for anonymously typed responses (namespace `GraphQL.Client.Abstractions`) you could achieve the same result with the following code: + +```csharp +var graphQLResponse = await graphQLClient.SendQueryAsync(personAndFilmsRequest, () => new { person = new Person()} ); +var personName = graphQLResponse.Data.person.Name; +``` ### Use Subscriptions From fb657d57e075e3da38b67ede30ef834ef206d987 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Wed, 6 May 2020 16:24:33 +0200 Subject: [PATCH 285/455] refine example --- README.md | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 08fc6cad..f748d12c 100644 --- a/README.md +++ b/README.md @@ -51,25 +51,26 @@ Be careful when using `byte[]` in your variables object, as most JSON serializer ### Execute Query/Mutation: ```csharp -public class PersonAndFilmsResponse { - public Person Person { get; set; } +public class ResponseType +{ + public PersonType Person { get; set; } } -public class Person { +public class PersonType +{ public string Name { get; set; } - public FilmConnectionContent FilmConnection { get; set; } + public FilmConnectionType FilmConnection { get; set; } +} - public class FilmConnectionContent { - public List Films { get; set; } +public class FilmConnectionType { + public List Films { get; set; } +} - public class FilmContent { - public string Title { get; set; } - } - } +public class FilmContentType { + public string Title { get; set; } } -var graphQLClient = new GraphQLHttpClient("https://swapi.apis.guru/", new NewtonsoftJsonSerializer()); -var graphQLResponse = await graphQLClient.SendQueryAsync(personAndFilmsRequest); +var graphQLResponse = await graphQLClient.SendQueryAsync(personAndFilmsRequest); var personName = graphQLResponse.Data.Person.Name; ``` @@ -77,7 +78,7 @@ var personName = graphQLResponse.Data.Person.Name; Using the extension method for anonymously typed responses (namespace `GraphQL.Client.Abstractions`) you could achieve the same result with the following code: ```csharp -var graphQLResponse = await graphQLClient.SendQueryAsync(personAndFilmsRequest, () => new { person = new Person()} ); +var graphQLResponse = await graphQLClient.SendQueryAsync(personAndFilmsRequest, () => new { person = new PersonType()} ); var personName = graphQLResponse.Data.person.Name; ``` From fa8253ebc22f5bb3944c721ba87e46cf968b8144 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Mon, 25 May 2020 21:44:22 +0200 Subject: [PATCH 286/455] use SelectMany to serialize calls to SendWebSocketRequestAsync --- src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs index 643df77e..652e3f77 100644 --- a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs +++ b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs @@ -3,6 +3,7 @@ using System.IO; using System.Net.Http; using System.Net.WebSockets; +using System.Reactive; using System.Reactive.Concurrency; using System.Reactive.Disposables; using System.Reactive.Linq; @@ -84,7 +85,8 @@ public GraphQLHttpWebSocket(Uri webSocketUri, GraphQLHttpClient client) _requestSubscription = _requestSubject .ObserveOn(_sendLoopScheduler) - .Subscribe(async request => await SendWebSocketRequestAsync(request)); + .SelectMany(SendWebSocketRequestAsync) + .Subscribe(); } #region Send requests @@ -339,14 +341,14 @@ private Task QueueWebSocketRequest(GraphQLWebSocketRequest request) return request.SendTask(); } - private async Task SendWebSocketRequestAsync(GraphQLWebSocketRequest request) + private async Task SendWebSocketRequestAsync(GraphQLWebSocketRequest request) { try { if (_internalCancellationToken.IsCancellationRequested) { request.SendCanceled(); - return; + return Unit.Default; } await InitializeWebSocket(); @@ -362,6 +364,7 @@ await _clientWebSocket.SendAsync( { request.SendFailed(e); } + return Unit.Default; } #endregion From 7a80649f1d32a517d86f6cb0437a7d57bffa74e7 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Mon, 25 May 2020 21:56:46 +0200 Subject: [PATCH 287/455] add default User-Agent header when not set --- src/GraphQL.Client/GraphQLHttpClient.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/GraphQL.Client/GraphQLHttpClient.cs b/src/GraphQL.Client/GraphQLHttpClient.cs index 5d8453ef..1f9a3b5a 100644 --- a/src/GraphQL.Client/GraphQLHttpClient.cs +++ b/src/GraphQL.Client/GraphQLHttpClient.cs @@ -2,7 +2,9 @@ using System.Collections.Concurrent; using System.Diagnostics; using System.IO; +using System.Linq; using System.Net.Http; +using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; using GraphQL.Client.Abstractions; @@ -57,6 +59,10 @@ public GraphQLHttpClient(GraphQLHttpClientOptions options, IGraphQLWebsocketJson Options = options ?? throw new ArgumentNullException(nameof(options)); JsonSerializer = serializer ?? throw new ArgumentNullException(nameof(serializer), "please configure the JSON serializer you want to use"); HttpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); + + if (!HttpClient.DefaultRequestHeaders.UserAgent.Any()) + HttpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(GetType().Assembly.GetName().Name, GetType().Assembly.GetName().Version.ToString())); + _graphQlHttpWebSocket = new GraphQLHttpWebSocket(GetWebSocketUri(), this); } From fc406dfec9a1c02c28a4a246c611ae5ba273746a Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Mon, 25 May 2020 22:05:47 +0200 Subject: [PATCH 288/455] update GitVersionTask (closes #214) --- src/src.props | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/src.props b/src/src.props index 91497142..c4e46be1 100644 --- a/src/src.props +++ b/src/src.props @@ -7,9 +7,8 @@ 8.0 - - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive From 059b32b2a58aae90a9d4eaff058ff808aac45162 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Mon, 25 May 2020 22:12:32 +0200 Subject: [PATCH 289/455] fix error handling in GraphQLLocalExecutionClient.ExecuteSubscriptionAsync (closes #236) --- .../GraphQLLocalExecutionClient.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs b/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs index d458d20e..f8fe6503 100644 --- a/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs +++ b/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs @@ -79,8 +79,11 @@ private async Task> ExecuteQueryAsync(Grap private async Task>> ExecuteSubscriptionAsync(GraphQLRequest request, CancellationToken cancellationToken = default) { var result = await ExecuteAsync(request, cancellationToken); - return ((SubscriptionExecutionResult)result).Streams?.Values.SingleOrDefault()? - .SelectMany(executionResult => Observable.FromAsync(token => ExecutionResultToGraphQLResponse(executionResult, token))); + var stream = ((SubscriptionExecutionResult)result).Streams?.Values.SingleOrDefault(); + + return stream == null + ? Observable.Throw>(new InvalidOperationException("the GraphQL execution did not return an observable")) + : stream.SelectMany(executionResult => Observable.FromAsync(token => ExecutionResultToGraphQLResponse(executionResult, token))); } private async Task ExecuteAsync(GraphQLRequest request, CancellationToken cancellationToken = default) From d1b8d228db36e6e6da572b95b8121b1e7018bf05 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Sat, 9 May 2020 21:56:32 +0200 Subject: [PATCH 290/455] add reproduction test --- .../BaseSerializerTest.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs b/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs index 5811823c..32dff92a 100644 --- a/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs +++ b/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs @@ -1,3 +1,4 @@ +using System; using System.IO; using System.Text; using System.Threading; @@ -118,5 +119,26 @@ public async void CanDoSerializationWithPredefinedTypes() Assert.Equal(message, response.Data.AddMessage.Content); } + + + public class WithNullable + { + public int? NullableInt { get; set; } + } + + [Fact] + public void CanSerializeNullableInt() + { + Action action = () => Serializer.SerializeToString(new GraphQLRequest + { + Query = "{}", + Variables = new WithNullable + { + NullableInt = 2 + } + }); + + action.Should().NotThrow(); + } } } From 191e139c2498b9da33b1de3b36f6e8cb0ce6d795 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Sun, 10 May 2020 00:47:57 +0200 Subject: [PATCH 291/455] test example response from spec, fix conversion --- .../ConverterHelperExtensions.cs | 25 +++++++ .../ErrorPathConverter.cs | 48 +++++++++++++ .../ImmutableConverter.cs | 7 ++ .../MapConverter.cs | 26 ++----- .../SystemTextJsonSerializer.cs | 1 + src/GraphQL.Primitives/ErrorPath.cs | 15 ++++ src/GraphQL.Primitives/GraphQLError.cs | 2 +- .../BaseSerializerTest.cs | 40 +++++++++-- .../TestData/DeserializeResponseTestData.cs | 71 +++++++++++++++++++ 9 files changed, 210 insertions(+), 25 deletions(-) create mode 100644 src/GraphQL.Client.Serializer.SystemTextJson/ConverterHelperExtensions.cs create mode 100644 src/GraphQL.Client.Serializer.SystemTextJson/ErrorPathConverter.cs create mode 100644 src/GraphQL.Primitives/ErrorPath.cs diff --git a/src/GraphQL.Client.Serializer.SystemTextJson/ConverterHelperExtensions.cs b/src/GraphQL.Client.Serializer.SystemTextJson/ConverterHelperExtensions.cs new file mode 100644 index 00000000..73daa8cf --- /dev/null +++ b/src/GraphQL.Client.Serializer.SystemTextJson/ConverterHelperExtensions.cs @@ -0,0 +1,25 @@ +using System; +using System.Numerics; +using System.Text.Json; + +namespace GraphQL.Client.Serializer.SystemTextJson +{ + public static class ConverterHelperExtensions + { + public static object ReadNumber(this JsonElement value) + { + if (value.TryGetInt32(out int i)) + return i; + else if (value.TryGetInt64(out long l)) + return l; + else if (BigInteger.TryParse(value.GetRawText(), out var bi)) + return bi; + else if (value.TryGetDouble(out double d)) + return d; + else if (value.TryGetDecimal(out decimal dd)) + return dd; + + throw new NotImplementedException($"Unexpected Number value. Raw text was: {value.GetRawText()}"); + } + } +} diff --git a/src/GraphQL.Client.Serializer.SystemTextJson/ErrorPathConverter.cs b/src/GraphQL.Client.Serializer.SystemTextJson/ErrorPathConverter.cs new file mode 100644 index 00000000..e760bdd6 --- /dev/null +++ b/src/GraphQL.Client.Serializer.SystemTextJson/ErrorPathConverter.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace GraphQL.Client.Serializer.SystemTextJson +{ + public class ErrorPathConverter : JsonConverter + { + + public override ErrorPath Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + using var doc = JsonDocument.ParseValue(ref reader); + + if (doc?.RootElement == null || doc?.RootElement.ValueKind != JsonValueKind.Array) + { + throw new ArgumentException("This converter can only parse when the root element is a JSON Object."); + } + + return new ErrorPath(ReadArray(doc.RootElement)); + } + + public override void Write(Utf8JsonWriter writer, ErrorPath value, JsonSerializerOptions options) + => throw new NotImplementedException( + "This converter currently is only intended to be used to read a JSON object into a strongly-typed representation."); + + private IEnumerable ReadArray(JsonElement value) + { + foreach (var item in value.EnumerateArray()) + { + yield return ReadValue(item); + } + } + + private object? ReadValue(JsonElement value) + => value.ValueKind switch + { + JsonValueKind.Number => value.ReadNumber(), + JsonValueKind.True => true, + JsonValueKind.False => false, + JsonValueKind.String => value.GetString(), + JsonValueKind.Null => null, + JsonValueKind.Undefined => null, + _ => throw new InvalidOperationException($"Unexpected value kind: {value.ValueKind}") + }; + } +} diff --git a/src/GraphQL.Client.Serializer.SystemTextJson/ImmutableConverter.cs b/src/GraphQL.Client.Serializer.SystemTextJson/ImmutableConverter.cs index 4a26f6f2..841b73d4 100644 --- a/src/GraphQL.Client.Serializer.SystemTextJson/ImmutableConverter.cs +++ b/src/GraphQL.Client.Serializer.SystemTextJson/ImmutableConverter.cs @@ -15,6 +15,13 @@ public class ImmutableConverter : JsonConverter { public override bool CanConvert(Type typeToConvert) { + if (typeToConvert.IsPrimitive) + return false; + + var nullableUnderlyingType = Nullable.GetUnderlyingType(typeToConvert); + if (nullableUnderlyingType != null && nullableUnderlyingType.IsPrimitive) + return false; + bool result; var constructors = typeToConvert.GetConstructors(BindingFlags.Public | BindingFlags.Instance); if (constructors.Length != 1) diff --git a/src/GraphQL.Client.Serializer.SystemTextJson/MapConverter.cs b/src/GraphQL.Client.Serializer.SystemTextJson/MapConverter.cs index 63d3d9fc..f9f61008 100644 --- a/src/GraphQL.Client.Serializer.SystemTextJson/MapConverter.cs +++ b/src/GraphQL.Client.Serializer.SystemTextJson/MapConverter.cs @@ -24,16 +24,16 @@ public override Map Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSeri throw new ArgumentException("This converter can only parse when the root element is a JSON Object."); } - return ReadDictionary(doc.RootElement); + return ReadDictionary(doc.RootElement, new Map()); } public override void Write(Utf8JsonWriter writer, Map value, JsonSerializerOptions options) => throw new NotImplementedException( "This converter currently is only intended to be used to read a JSON object into a strongly-typed representation."); - private TDictionary ReadDictionary(JsonElement element) where TDictionary : Dictionary + private TDictionary ReadDictionary(JsonElement element, TDictionary result) + where TDictionary : Dictionary { - var result = Activator.CreateInstance(); foreach (var property in element.EnumerateObject()) { result[property.Name] = ReadValue(property.Value); @@ -53,8 +53,8 @@ private TDictionary ReadDictionary(JsonElement element) where TDict => value.ValueKind switch { JsonValueKind.Array => ReadArray(value).ToList(), - JsonValueKind.Object => ReadDictionary>(value), - JsonValueKind.Number => ReadNumber(value), + JsonValueKind.Object => ReadDictionary(value, new Dictionary()), + JsonValueKind.Number => value.ReadNumber(), JsonValueKind.True => true, JsonValueKind.False => false, JsonValueKind.String => value.GetString(), @@ -63,20 +63,6 @@ private TDictionary ReadDictionary(JsonElement element) where TDict _ => throw new InvalidOperationException($"Unexpected value kind: {value.ValueKind}") }; - private object ReadNumber(JsonElement value) - { - if (value.TryGetInt32(out int i)) - return i; - else if (value.TryGetInt64(out long l)) - return l; - else if (BigInteger.TryParse(value.GetRawText(), out var bi)) - return bi; - else if (value.TryGetDouble(out double d)) - return d; - else if (value.TryGetDecimal(out decimal dd)) - return dd; - - throw new NotImplementedException($"Unexpected Number value. Raw text was: {value.GetRawText()}"); - } + } } diff --git a/src/GraphQL.Client.Serializer.SystemTextJson/SystemTextJsonSerializer.cs b/src/GraphQL.Client.Serializer.SystemTextJson/SystemTextJsonSerializer.cs index f5b2e7f4..a1667ec8 100644 --- a/src/GraphQL.Client.Serializer.SystemTextJson/SystemTextJsonSerializer.cs +++ b/src/GraphQL.Client.Serializer.SystemTextJson/SystemTextJsonSerializer.cs @@ -30,6 +30,7 @@ public SystemTextJsonSerializer(JsonSerializerOptions options) private void ConfigureMandatorySerializerOptions() { // deserialize extensions to Dictionary + Options.Converters.Insert(0, new ErrorPathConverter()); Options.Converters.Insert(0, new MapConverter()); // allow the JSON field "data" to match the property "Data" even without JsonNamingPolicy.CamelCase Options.PropertyNameCaseInsensitive = true; diff --git a/src/GraphQL.Primitives/ErrorPath.cs b/src/GraphQL.Primitives/ErrorPath.cs new file mode 100644 index 00000000..59abdb79 --- /dev/null +++ b/src/GraphQL.Primitives/ErrorPath.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace GraphQL +{ + public class ErrorPath : List + { + public ErrorPath() + { + } + + public ErrorPath(IEnumerable collection) : base(collection) + { + } + } +} diff --git a/src/GraphQL.Primitives/GraphQLError.cs b/src/GraphQL.Primitives/GraphQLError.cs index 9b7cfc99..c76d3f00 100644 --- a/src/GraphQL.Primitives/GraphQLError.cs +++ b/src/GraphQL.Primitives/GraphQLError.cs @@ -26,7 +26,7 @@ public class GraphQLError : IEquatable /// The Path of the error /// [DataMember(Name = "path")] - public object[]? Path { get; set; } + public ErrorPath? Path { get; set; } /// /// The extensions of the error diff --git a/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs b/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs index 32dff92a..e8bd28db 100644 --- a/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs +++ b/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs @@ -1,8 +1,12 @@ using System; using System.IO; +using System.Linq; +using System.Reflection; using System.Text; using System.Threading; +using System.Threading.Tasks; using FluentAssertions; +using FluentAssertions.Execution; using GraphQL.Client.Abstractions; using GraphQL.Client.Abstractions.Websocket; using GraphQL.Client.LocalExecution; @@ -49,14 +53,31 @@ public void SerializeToBytesTest(string expectedJson, GraphQLWebSocketRequest re [Theory] [ClassData(typeof(DeserializeResponseTestData))] - public async void DeserializeFromUtf8StreamTest(string json, GraphQLResponse expectedResponse) + public async void DeserializeFromUtf8StreamTest(string json, IGraphQLResponse expectedResponse) { var jsonBytes = Encoding.UTF8.GetBytes(json); await using var ms = new MemoryStream(jsonBytes); - var response = await Serializer.DeserializeFromUtf8StreamAsync>(ms, CancellationToken.None); + var response = await DeserializeToUnknownType(expectedResponse.Data?.GetType() ?? typeof(object), ms); + //var response = await Serializer.DeserializeFromUtf8StreamAsync(ms, CancellationToken.None); - response.Data.Should().BeEquivalentTo(expectedResponse.Data); - response.Errors.Should().Equal(expectedResponse.Errors); + response.Data.Should().BeEquivalentTo(expectedResponse.Data, options => options.WithAutoConversion()); + + if (expectedResponse.Errors is null) + response.Errors.Should().BeNull(); + else { + using (new AssertionScope()) + { + response.Errors.Should().NotBeNull(); + response.Errors.Should().HaveSameCount(expectedResponse.Errors); + for (int i = 0; i < expectedResponse.Errors.Length; i++) + { + response.Errors[i].Message.Should().BeEquivalentTo(expectedResponse.Errors[i].Message); + response.Errors[i].Locations.Should().BeEquivalentTo(expectedResponse.Errors[i].Locations?.ToList()); + response.Errors[i].Path.Should().BeEquivalentTo(expectedResponse.Errors[i].Path); + response.Errors[i].Extensions.Should().BeEquivalentTo(expectedResponse.Errors[i].Extensions); + } + } + } if (expectedResponse.Extensions == null) response.Extensions.Should().BeNull(); @@ -70,6 +91,17 @@ public async void DeserializeFromUtf8StreamTest(string json, GraphQLResponse DeserializeToUnknownType(Type dataType, Stream stream) + { + MethodInfo mi = Serializer.GetType().GetMethod("DeserializeFromUtf8StreamAsync", BindingFlags.Instance | BindingFlags.Public); + MethodInfo mi2 = mi.MakeGenericMethod(dataType); + var task = (Task) mi2.Invoke(Serializer, new object[] { stream, CancellationToken.None }); + await task; + var resultProperty = task.GetType().GetProperty("Result", BindingFlags.Public | BindingFlags.Instance); + var result = resultProperty.GetValue(task); + return (IGraphQLResponse)result; + } + [Fact] public async void CanDeserializeExtensions() { diff --git a/tests/GraphQL.Client.Serializer.Tests/TestData/DeserializeResponseTestData.cs b/tests/GraphQL.Client.Serializer.Tests/TestData/DeserializeResponseTestData.cs index 0c6844cf..5f1dcdf5 100644 --- a/tests/GraphQL.Client.Serializer.Tests/TestData/DeserializeResponseTestData.cs +++ b/tests/GraphQL.Client.Serializer.Tests/TestData/DeserializeResponseTestData.cs @@ -37,8 +37,79 @@ public IEnumerator GetEnumerator() } } }; + + yield return new object[] + { + @"{ + ""errors"": [ + { + ""message"": ""Name for character with ID 1002 could not be fetched."", + ""locations"": [ + { + ""line"": 6, + ""column"": 7 + } + ], + ""path"": [ + ""hero"", + ""heroFriends"", + 1, + ""name"" + ] + } + ], + ""data"": { + ""hero"": { + ""name"": ""R2-D2"", + ""heroFriends"": [ + { + ""id"": ""1000"", + ""name"": ""Luke Skywalker"" + }, + { + ""id"": ""1002"", + ""name"": null + }, + { + ""id"": ""1003"", + ""name"": ""Leia Organa"" + } + ] + } + } + }", + NewAnonymouslyTypedGraphQLResponse(new + { + hero = new + { + name = "R2-D2", + heroFriends = new List + { + new Friend {Id = "1000", Name = "Luke Skywalker"}, + new Friend {Id = "1002", Name = null}, + new Friend {Id = "1003", Name = "Leia Organa"} + } + } + }, + new[] { + new GraphQLError { + Message = "Name for character with ID 1002 could not be fetched.", + Locations = new [] { new GraphQLLocation{Line = 6, Column = 7 }}, + Path = new ErrorPath{"hero", "heroFriends", 1, "name"} + } + }) + }; } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + private GraphQLResponse NewAnonymouslyTypedGraphQLResponse(T data, GraphQLError[]? errors = null, Map? extensions = null) + => new GraphQLResponse {Data = data, Errors = errors, Extensions = extensions}; + } + + public class Friend + { + public string Id { get; set; } + public string? Name { get; set; } } } From 52dc9bd9edb173cc8ebf6e13aa6e7f18af8d37c1 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Mon, 25 May 2020 23:04:27 +0200 Subject: [PATCH 292/455] remove usage of evil JsonDocument.ParseValue --- .../ConverterHelperExtensions.cs | 32 +++++--- .../ErrorPathConverter.cs | 49 ++++++------ .../MapConverter.cs | 74 +++++++++++-------- 3 files changed, 93 insertions(+), 62 deletions(-) diff --git a/src/GraphQL.Client.Serializer.SystemTextJson/ConverterHelperExtensions.cs b/src/GraphQL.Client.Serializer.SystemTextJson/ConverterHelperExtensions.cs index 73daa8cf..fbb9036d 100644 --- a/src/GraphQL.Client.Serializer.SystemTextJson/ConverterHelperExtensions.cs +++ b/src/GraphQL.Client.Serializer.SystemTextJson/ConverterHelperExtensions.cs @@ -1,25 +1,37 @@ using System; +using System.Buffers; using System.Numerics; +using System.Text; using System.Text.Json; namespace GraphQL.Client.Serializer.SystemTextJson { public static class ConverterHelperExtensions { - public static object ReadNumber(this JsonElement value) + public static object ReadNumber(this ref Utf8JsonReader reader) { - if (value.TryGetInt32(out int i)) + if (reader.TryGetInt32(out int i)) return i; - else if (value.TryGetInt64(out long l)) + else if (reader.TryGetInt64(out long l)) return l; - else if (BigInteger.TryParse(value.GetRawText(), out var bi)) - return bi; - else if (value.TryGetDouble(out double d)) - return d; - else if (value.TryGetDecimal(out decimal dd)) - return dd; + else if (reader.TryGetDouble(out double d)) + return reader.TryGetBigInteger(out var bi) && bi != new BigInteger(d) + ? bi + : (object)d; + else if (reader.TryGetDecimal(out decimal dd)) + return reader.TryGetBigInteger(out var bi) && bi != new BigInteger(dd) + ? bi + : (object)dd; - throw new NotImplementedException($"Unexpected Number value. Raw text was: {value.GetRawText()}"); + throw new NotImplementedException($"Unexpected Number value. Raw text was: {reader.GetRawString()}"); + } + + public static bool TryGetBigInteger(this ref Utf8JsonReader reader, out BigInteger bi) => BigInteger.TryParse(reader.GetRawString(), out bi); + + public static string GetRawString(this ref Utf8JsonReader reader) + { + var byteArray = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan.ToArray(); + return Encoding.UTF8.GetString(byteArray); } } } diff --git a/src/GraphQL.Client.Serializer.SystemTextJson/ErrorPathConverter.cs b/src/GraphQL.Client.Serializer.SystemTextJson/ErrorPathConverter.cs index e760bdd6..b2718096 100644 --- a/src/GraphQL.Client.Serializer.SystemTextJson/ErrorPathConverter.cs +++ b/src/GraphQL.Client.Serializer.SystemTextJson/ErrorPathConverter.cs @@ -9,40 +9,43 @@ namespace GraphQL.Client.Serializer.SystemTextJson public class ErrorPathConverter : JsonConverter { - public override ErrorPath Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - using var doc = JsonDocument.ParseValue(ref reader); - - if (doc?.RootElement == null || doc?.RootElement.ValueKind != JsonValueKind.Array) - { - throw new ArgumentException("This converter can only parse when the root element is a JSON Object."); - } - - return new ErrorPath(ReadArray(doc.RootElement)); - } + public override ErrorPath Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => + new ErrorPath(ReadArray(ref reader)); public override void Write(Utf8JsonWriter writer, ErrorPath value, JsonSerializerOptions options) => throw new NotImplementedException( "This converter currently is only intended to be used to read a JSON object into a strongly-typed representation."); - private IEnumerable ReadArray(JsonElement value) + private IEnumerable ReadArray(ref Utf8JsonReader reader) { - foreach (var item in value.EnumerateArray()) + if (reader.TokenType != JsonTokenType.StartArray) { - yield return ReadValue(item); + throw new JsonException("This converter can only parse when the root element is a JSON Array."); } + + var array = new List(); + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndArray) + break; + + array.Add(ReadValue(ref reader)); + } + + return array; } - private object? ReadValue(JsonElement value) - => value.ValueKind switch + private object? ReadValue(ref Utf8JsonReader reader) + => reader.TokenType switch { - JsonValueKind.Number => value.ReadNumber(), - JsonValueKind.True => true, - JsonValueKind.False => false, - JsonValueKind.String => value.GetString(), - JsonValueKind.Null => null, - JsonValueKind.Undefined => null, - _ => throw new InvalidOperationException($"Unexpected value kind: {value.ValueKind}") + JsonTokenType.None => null, + JsonTokenType.String => reader.GetString(), + JsonTokenType.Number => reader.ReadNumber(), + JsonTokenType.True => true, + JsonTokenType.False => false, + JsonTokenType.Null => null, + _ => throw new InvalidOperationException($"Unexpected token type: {reader.TokenType}") }; } } diff --git a/src/GraphQL.Client.Serializer.SystemTextJson/MapConverter.cs b/src/GraphQL.Client.Serializer.SystemTextJson/MapConverter.cs index f9f61008..e41c1bf5 100644 --- a/src/GraphQL.Client.Serializer.SystemTextJson/MapConverter.cs +++ b/src/GraphQL.Client.Serializer.SystemTextJson/MapConverter.cs @@ -15,52 +15,68 @@ namespace GraphQL.Client.Serializer.SystemTextJson /// public class MapConverter : JsonConverter { - public override Map Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - using var doc = JsonDocument.ParseValue(ref reader); - - if (doc?.RootElement == null || doc?.RootElement.ValueKind != JsonValueKind.Object) - { - throw new ArgumentException("This converter can only parse when the root element is a JSON Object."); - } - - return ReadDictionary(doc.RootElement, new Map()); - } + public override Map Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => ReadDictionary(ref reader, new Map()); public override void Write(Utf8JsonWriter writer, Map value, JsonSerializerOptions options) => throw new NotImplementedException( "This converter currently is only intended to be used to read a JSON object into a strongly-typed representation."); - private TDictionary ReadDictionary(JsonElement element, TDictionary result) + private static TDictionary ReadDictionary(ref Utf8JsonReader reader, TDictionary result) where TDictionary : Dictionary { - foreach (var property in element.EnumerateObject()) + if (reader.TokenType != JsonTokenType.StartObject) + throw new JsonException(); + + while (reader.Read()) { - result[property.Name] = ReadValue(property.Value); + if (reader.TokenType == JsonTokenType.EndObject) + break; + + if (reader.TokenType != JsonTokenType.PropertyName) + throw new JsonException(); + + string key = reader.GetString(); + + // move to property value + if (!reader.Read()) + throw new JsonException(); + + result.Add(key, ReadValue(ref reader)); } + return result; } - private IEnumerable ReadArray(JsonElement value) + private static List ReadArray(ref Utf8JsonReader reader) { - foreach (var item in value.EnumerateArray()) + if (reader.TokenType != JsonTokenType.StartArray) + throw new JsonException(); + + var result = new List(); + + while (reader.Read()) { - yield return ReadValue(item); + if (reader.TokenType == JsonTokenType.EndArray) + break; + + result.Add(ReadValue(ref reader)); } - } - private object? ReadValue(JsonElement value) - => value.ValueKind switch + return result; + } + + private static object? ReadValue(ref Utf8JsonReader reader) + => reader.TokenType switch { - JsonValueKind.Array => ReadArray(value).ToList(), - JsonValueKind.Object => ReadDictionary(value, new Dictionary()), - JsonValueKind.Number => value.ReadNumber(), - JsonValueKind.True => true, - JsonValueKind.False => false, - JsonValueKind.String => value.GetString(), - JsonValueKind.Null => null, - JsonValueKind.Undefined => null, - _ => throw new InvalidOperationException($"Unexpected value kind: {value.ValueKind}") + JsonTokenType.StartArray => ReadArray(ref reader).ToList(), + JsonTokenType.StartObject => ReadDictionary(ref reader, new Dictionary()), + JsonTokenType.Number => reader.ReadNumber(), + JsonTokenType.True => true, + JsonTokenType.False => false, + JsonTokenType.String => reader.GetString(), + JsonTokenType.Null => null, + JsonTokenType.None => null, + _ => throw new InvalidOperationException($"Unexpected value kind: {reader.TokenType}") }; From 99814cc5db72fd0a518d8ab8d970bf0f5e2f70fe Mon Sep 17 00:00:00 2001 From: Marius Wirtherle Date: Tue, 26 May 2020 12:27:57 +0200 Subject: [PATCH 293/455] don't use ImmutableConverter for nullable structs --- .../ImmutableConverter.cs | 7 ++-- .../BaseSerializerTest.cs | 34 +++++++++++++++---- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/src/GraphQL.Client.Serializer.SystemTextJson/ImmutableConverter.cs b/src/GraphQL.Client.Serializer.SystemTextJson/ImmutableConverter.cs index 841b73d4..fa0a3f06 100644 --- a/src/GraphQL.Client.Serializer.SystemTextJson/ImmutableConverter.cs +++ b/src/GraphQL.Client.Serializer.SystemTextJson/ImmutableConverter.cs @@ -19,7 +19,7 @@ public override bool CanConvert(Type typeToConvert) return false; var nullableUnderlyingType = Nullable.GetUnderlyingType(typeToConvert); - if (nullableUnderlyingType != null && nullableUnderlyingType.IsPrimitive) + if (nullableUnderlyingType != null && nullableUnderlyingType.IsValueType) return false; bool result; @@ -40,7 +40,7 @@ public override bool CanConvert(Type typeToConvert) foreach (var parameter in parameters) { var hasMatchingProperty = properties.Any(p => - NameOfPropertyAndParameter.Matches(p.Name, parameter.Name, typeToConvert.IsAnonymous())); + NameOfPropertyAndParameter.Matches(p.Name, parameter.Name, typeToConvert.IsAnonymous())); if (!hasMatchingProperty) { result = false; @@ -90,7 +90,7 @@ public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonS { var parameterInfo = parameters[index]; var value = valueOfProperty.First(prop => - NameOfPropertyAndParameter.Matches(prop.Key.Name, parameterInfo.Name, typeToConvert.IsAnonymous())).Value; + NameOfPropertyAndParameter.Matches(prop.Key.Name, parameterInfo.Name, typeToConvert.IsAnonymous())).Value; parameterValues[index] = value; } @@ -120,6 +120,7 @@ public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOp if (!(converter is ImmutableConverter)) strippedOptions.Converters.Add(converter); } + JsonSerializer.Serialize(writer, value, strippedOptions); } diff --git a/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs b/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs index e8bd28db..53cba0c1 100644 --- a/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs +++ b/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs @@ -58,13 +58,15 @@ public async void DeserializeFromUtf8StreamTest(string json, IGraphQLResponse ex var jsonBytes = Encoding.UTF8.GetBytes(json); await using var ms = new MemoryStream(jsonBytes); var response = await DeserializeToUnknownType(expectedResponse.Data?.GetType() ?? typeof(object), ms); + //var response = await Serializer.DeserializeFromUtf8StreamAsync(ms, CancellationToken.None); response.Data.Should().BeEquivalentTo(expectedResponse.Data, options => options.WithAutoConversion()); if (expectedResponse.Errors is null) response.Errors.Should().BeNull(); - else { + else + { using (new AssertionScope()) { response.Errors.Should().NotBeNull(); @@ -95,7 +97,7 @@ public async Task DeserializeToUnknownType(Type dataType, Stre { MethodInfo mi = Serializer.GetType().GetMethod("DeserializeFromUtf8StreamAsync", BindingFlags.Instance | BindingFlags.Public); MethodInfo mi2 = mi.MakeGenericMethod(dataType); - var task = (Task) mi2.Invoke(Serializer, new object[] { stream, CancellationToken.None }); + var task = (Task)mi2.Invoke(Serializer, new object[] { stream, CancellationToken.None }); await task; var resultProperty = task.GetType().GetProperty("Result", BindingFlags.Public | BindingFlags.Instance); var result = resultProperty.GetValue(task); @@ -105,9 +107,8 @@ public async Task DeserializeToUnknownType(Type dataType, Stre [Fact] public async void CanDeserializeExtensions() { - var response = await ChatClient.SendQueryAsync(new GraphQLRequest("query { extensionsTest }"), - () => new { extensionsTest = "" }) + () => new { extensionsTest = "" }) ; response.Errors.Should().NotBeNull(); @@ -134,8 +135,8 @@ query Droid($id: String!) { name } }", - new { id = id.ToString() }, - "Human"); + new { id = id.ToString() }, + "Human"); var response = await StarWarsClient.SendQueryAsync(graphQLRequest, () => new { Human = new { Name = string.Empty } }); @@ -152,7 +153,6 @@ public async void CanDoSerializationWithPredefinedTypes() Assert.Equal(message, response.Data.AddMessage.Content); } - public class WithNullable { public int? NullableInt { get; set; } @@ -172,5 +172,25 @@ public void CanSerializeNullableInt() action.Should().NotThrow(); } + + public class WithNullableStruct + { + public DateTime? NullableStruct { get; set; } + } + + [Fact] + public void CanSerializeNullableStruct() + { + Action action = () => Serializer.SerializeToString(new GraphQLRequest + { + Query = "{}", + Variables = new WithNullableStruct + { + NullableStruct = DateTime.Now + } + }); + + action.Should().NotThrow(); + } } } From b5331ff25a580f1a38030f4720c6d0cad8c5e100 Mon Sep 17 00:00:00 2001 From: Eilon Lipton Date: Mon, 1 Jun 2020 15:32:21 -0700 Subject: [PATCH 294/455] Readme updates - Changed HTTP URL to HTTPS - Fixed list formatting - Added sample to show creation of GraphQLHttpClient --- README.md | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f748d12c..1e787251 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,19 @@ A GraphQL Client for .NET Standard over HTTP. ## Specification: The Library will try to follow the following standards and documents: -[GraphQL Specification](https://facebook.github.io/graphql/June2018) -[GraphQL HomePage](http://graphql.org/learn) + +* [GraphQL Specification](https://facebook.github.io/graphql/June2018) +* [GraphQL HomePage](https://graphql.org/learn) ## Usage: +### Create a GraphQLHttpClient + +```csharp +// To use NewtonsoftJsonSerializer, add a reference to NuGet package GraphQL.Client.Serializer.Newtonsoft +var graphQLClient = new GraphQLHttpClient("https://api.example.com/graphql", new NewtonsoftJsonSerializer()); +``` + ### Create a GraphQLRequest: #### Simple Request: ```csharp @@ -124,9 +132,10 @@ subscription.Dispose(); ``` ## Useful Links: -[StarWars Example Server (GitHub)](https://github.com/graphql/swapi-graphql) -[StarWars Example Server (EndPoint)](https://swapi.apis.guru/) -[GitHub GraphQL API Docs](https://developer.github.com/v4/guides/forming-calls/) -[GitHub GraphQL Explorer](https://developer.github.com/v4/explorer/) -[GitHub GraphQL Endpoint](https://api.github.com/graphql) +* [StarWars Example Server (GitHub)](https://github.com/graphql/swapi-graphql) +* [StarWars Example Server (EndPoint)](https://swapi.apis.guru/) + +* [GitHub GraphQL API Docs](https://developer.github.com/v4/guides/forming-calls/) +* [GitHub GraphQL Explorer](https://developer.github.com/v4/explorer/) +* [GitHub GraphQL Endpoint](https://api.github.com/graphql) From 87dbf3efc0d5d63851c6f37d4383670e3240ee66 Mon Sep 17 00:00:00 2001 From: Colony5 <53336327+Maydayof@users.noreply.github.com> Date: Wed, 17 Jun 2020 09:56:49 +0200 Subject: [PATCH 295/455] Create new GraphQLHttpWebSocket instance if needed --- src/GraphQL.Client/GraphQLHttpClient.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/GraphQL.Client/GraphQLHttpClient.cs b/src/GraphQL.Client/GraphQLHttpClient.cs index 1f9a3b5a..5288da0a 100644 --- a/src/GraphQL.Client/GraphQLHttpClient.cs +++ b/src/GraphQL.Client/GraphQLHttpClient.cs @@ -15,7 +15,7 @@ namespace GraphQL.Client.Http { public class GraphQLHttpClient : IGraphQLClient { - private readonly GraphQLHttpWebSocket _graphQlHttpWebSocket; + private readonly GraphQLHttpWebSocket _graphQlHttpWebSocket = null; private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); private readonly ConcurrentDictionary, object> _subscriptionStreams = new ConcurrentDictionary, object>(); @@ -63,7 +63,8 @@ public GraphQLHttpClient(GraphQLHttpClientOptions options, IGraphQLWebsocketJson if (!HttpClient.DefaultRequestHeaders.UserAgent.Any()) HttpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(GetType().Assembly.GetName().Name, GetType().Assembly.GetName().Version.ToString())); - _graphQlHttpWebSocket = new GraphQLHttpWebSocket(GetWebSocketUri(), this); + if (options.UseWebSocketForQueriesAndMutations) + _graphQlHttpWebSocket = new GraphQLHttpWebSocket(GetWebSocketUri(), this); } #endregion @@ -186,7 +187,7 @@ protected virtual void Dispose(bool disposing) Debug.WriteLine($"Disposing GraphQLHttpClient on endpoint {Options.EndPoint}"); _cancellationTokenSource.Cancel(); HttpClient.Dispose(); - _graphQlHttpWebSocket.Dispose(); + _graphQlHttpWebSocket?.Dispose(); _cancellationTokenSource.Dispose(); } } From 5dd4bfcdb56368d759dfcc47e8387b7d1a7132bc Mon Sep 17 00:00:00 2001 From: Colony5 <53336327+Maydayof@users.noreply.github.com> Date: Wed, 17 Jun 2020 12:26:56 +0200 Subject: [PATCH 296/455] Subscriptions always use websocket connection --- src/GraphQL.Client/GraphQLHttpClient.cs | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/GraphQL.Client/GraphQLHttpClient.cs b/src/GraphQL.Client/GraphQLHttpClient.cs index 5288da0a..a9cd48e0 100644 --- a/src/GraphQL.Client/GraphQLHttpClient.cs +++ b/src/GraphQL.Client/GraphQLHttpClient.cs @@ -15,7 +15,7 @@ namespace GraphQL.Client.Http { public class GraphQLHttpClient : IGraphQLClient { - private readonly GraphQLHttpWebSocket _graphQlHttpWebSocket = null; + private GraphQLHttpWebSocket _graphQlHttpWebSocket = null; private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); private readonly ConcurrentDictionary, object> _subscriptionStreams = new ConcurrentDictionary, object>(); @@ -37,12 +37,12 @@ public class GraphQLHttpClient : IGraphQLClient /// /// Publishes all exceptions which occur inside the websocket receive stream (i.e. for logging purposes) /// - public IObservable WebSocketReceiveErrors => _graphQlHttpWebSocket.ReceiveErrors; + public IObservable WebSocketReceiveErrors => GetGraphQLHttpWebSocket().ReceiveErrors; /// /// the websocket connection state /// - public IObservable WebsocketConnectionState => _graphQlHttpWebSocket.ConnectionState; + public IObservable WebsocketConnectionState => GetGraphQLHttpWebSocket().ConnectionState; #region Constructors @@ -75,7 +75,7 @@ public GraphQLHttpClient(GraphQLHttpClientOptions options, IGraphQLWebsocketJson public async Task> SendQueryAsync(GraphQLRequest request, CancellationToken cancellationToken = default) { if (Options.UseWebSocketForQueriesAndMutations) - return await _graphQlHttpWebSocket.SendRequest(request, cancellationToken); + return await GetGraphQLHttpWebSocket().SendRequest(request, cancellationToken); return await SendHttpRequestAsync(request, cancellationToken); } @@ -96,7 +96,7 @@ public IObservable> CreateSubscriptionStream>)_subscriptionStreams[key]; - var observable = _graphQlHttpWebSocket.CreateSubscriptionStream(request); + var observable = GetGraphQLHttpWebSocket().CreateSubscriptionStream(request); _subscriptionStreams.TryAdd(key, observable); return observable; @@ -113,7 +113,7 @@ public IObservable> CreateSubscriptionStream>)_subscriptionStreams[key]; - var observable = _graphQlHttpWebSocket.CreateSubscriptionStream(request, exceptionHandler); + var observable = GetGraphQLHttpWebSocket().CreateSubscriptionStream(request, exceptionHandler); _subscriptionStreams.TryAdd(key, observable); return observable; } @@ -124,7 +124,7 @@ public IObservable> CreateSubscriptionStream /// - public Task InitializeWebsocketConnection() => _graphQlHttpWebSocket.InitializeWebSocket(); + public Task InitializeWebsocketConnection() => GetGraphQLHttpWebSocket().InitializeWebSocket(); #region Private Methods @@ -158,6 +158,15 @@ private Uri GetWebSocketUri() return new Uri($"{webSocketSchema}://{Options.EndPoint.Host}:{Options.EndPoint.Port}{Options.EndPoint.AbsolutePath}"); } + private GraphQLHttpWebSocket GetGraphQLHttpWebSocket() + { + //no instance + if (_graphQlHttpWebSocket == null) + _graphQlHttpWebSocket = new GraphQLHttpWebSocket(GetWebSocketUri(), this); + + return _graphQlHttpWebSocket; + } + #endregion #region IDisposable From 3106d0e5109d0d5a1cd0d77d3dffaaab0d93076a Mon Sep 17 00:00:00 2001 From: Colony5 <53336327+Maydayof@users.noreply.github.com> Date: Mon, 22 Jun 2020 13:49:03 +0200 Subject: [PATCH 297/455] Use Lazy instead of singleton pattern --- src/GraphQL.Client/GraphQLHttpClient.cs | 30 +++++++++---------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/src/GraphQL.Client/GraphQLHttpClient.cs b/src/GraphQL.Client/GraphQLHttpClient.cs index a9cd48e0..8d312baa 100644 --- a/src/GraphQL.Client/GraphQLHttpClient.cs +++ b/src/GraphQL.Client/GraphQLHttpClient.cs @@ -15,7 +15,9 @@ namespace GraphQL.Client.Http { public class GraphQLHttpClient : IGraphQLClient { - private GraphQLHttpWebSocket _graphQlHttpWebSocket = null; + private readonly Lazy _lazyHttpWebSocket; + private GraphQLHttpWebSocket _graphQlHttpWebSocket => _lazyHttpWebSocket.Value; + private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); private readonly ConcurrentDictionary, object> _subscriptionStreams = new ConcurrentDictionary, object>(); @@ -37,12 +39,12 @@ public class GraphQLHttpClient : IGraphQLClient /// /// Publishes all exceptions which occur inside the websocket receive stream (i.e. for logging purposes) /// - public IObservable WebSocketReceiveErrors => GetGraphQLHttpWebSocket().ReceiveErrors; + public IObservable WebSocketReceiveErrors => _graphQlHttpWebSocket.ReceiveErrors; /// /// the websocket connection state /// - public IObservable WebsocketConnectionState => GetGraphQLHttpWebSocket().ConnectionState; + public IObservable WebsocketConnectionState => _graphQlHttpWebSocket.ConnectionState; #region Constructors @@ -63,8 +65,7 @@ public GraphQLHttpClient(GraphQLHttpClientOptions options, IGraphQLWebsocketJson if (!HttpClient.DefaultRequestHeaders.UserAgent.Any()) HttpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(GetType().Assembly.GetName().Name, GetType().Assembly.GetName().Version.ToString())); - if (options.UseWebSocketForQueriesAndMutations) - _graphQlHttpWebSocket = new GraphQLHttpWebSocket(GetWebSocketUri(), this); + _lazyHttpWebSocket = new Lazy(() => new GraphQLHttpWebSocket(GetWebSocketUri(), this)); } #endregion @@ -75,7 +76,7 @@ public GraphQLHttpClient(GraphQLHttpClientOptions options, IGraphQLWebsocketJson public async Task> SendQueryAsync(GraphQLRequest request, CancellationToken cancellationToken = default) { if (Options.UseWebSocketForQueriesAndMutations) - return await GetGraphQLHttpWebSocket().SendRequest(request, cancellationToken); + return await _graphQlHttpWebSocket.SendRequest(request, cancellationToken); return await SendHttpRequestAsync(request, cancellationToken); } @@ -96,7 +97,7 @@ public IObservable> CreateSubscriptionStream>)_subscriptionStreams[key]; - var observable = GetGraphQLHttpWebSocket().CreateSubscriptionStream(request); + var observable = _graphQlHttpWebSocket.CreateSubscriptionStream(request); _subscriptionStreams.TryAdd(key, observable); return observable; @@ -113,7 +114,7 @@ public IObservable> CreateSubscriptionStream>)_subscriptionStreams[key]; - var observable = GetGraphQLHttpWebSocket().CreateSubscriptionStream(request, exceptionHandler); + var observable = _graphQlHttpWebSocket.CreateSubscriptionStream(request, exceptionHandler); _subscriptionStreams.TryAdd(key, observable); return observable; } @@ -124,7 +125,7 @@ public IObservable> CreateSubscriptionStream /// - public Task InitializeWebsocketConnection() => GetGraphQLHttpWebSocket().InitializeWebSocket(); + public Task InitializeWebsocketConnection() => _graphQlHttpWebSocket.InitializeWebSocket(); #region Private Methods @@ -158,15 +159,6 @@ private Uri GetWebSocketUri() return new Uri($"{webSocketSchema}://{Options.EndPoint.Host}:{Options.EndPoint.Port}{Options.EndPoint.AbsolutePath}"); } - private GraphQLHttpWebSocket GetGraphQLHttpWebSocket() - { - //no instance - if (_graphQlHttpWebSocket == null) - _graphQlHttpWebSocket = new GraphQLHttpWebSocket(GetWebSocketUri(), this); - - return _graphQlHttpWebSocket; - } - #endregion #region IDisposable @@ -196,7 +188,7 @@ protected virtual void Dispose(bool disposing) Debug.WriteLine($"Disposing GraphQLHttpClient on endpoint {Options.EndPoint}"); _cancellationTokenSource.Cancel(); HttpClient.Dispose(); - _graphQlHttpWebSocket?.Dispose(); + _lazyHttpWebSocket?.Value.Dispose(); _cancellationTokenSource.Dispose(); } } From d644f032b359a9547348358e1864c1254230ebff Mon Sep 17 00:00:00 2001 From: Maydayof <53336327+Maydayof@users.noreply.github.com> Date: Mon, 22 Jun 2020 15:15:00 +0200 Subject: [PATCH 298/455] Update src/GraphQL.Client/GraphQLHttpClient.cs Dispose if has value Co-authored-by: Alexander Rose --- src/GraphQL.Client/GraphQLHttpClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GraphQL.Client/GraphQLHttpClient.cs b/src/GraphQL.Client/GraphQLHttpClient.cs index 8d312baa..3ecb12b1 100644 --- a/src/GraphQL.Client/GraphQLHttpClient.cs +++ b/src/GraphQL.Client/GraphQLHttpClient.cs @@ -188,7 +188,7 @@ protected virtual void Dispose(bool disposing) Debug.WriteLine($"Disposing GraphQLHttpClient on endpoint {Options.EndPoint}"); _cancellationTokenSource.Cancel(); HttpClient.Dispose(); - _lazyHttpWebSocket?.Value.Dispose(); + if(_lazyHttpWebSocket?.HasValue) _lazyHttpWebSocket?.Value.Dispose(); _cancellationTokenSource.Dispose(); } } From 1de57669101a4d7687d0440059a442cd915037cf Mon Sep 17 00:00:00 2001 From: Colony5 <53336327+Maydayof@users.noreply.github.com> Date: Mon, 22 Jun 2020 15:25:35 +0200 Subject: [PATCH 299/455] Dispose HttpWebSocket if created --- src/GraphQL.Client/GraphQLHttpClient.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/GraphQL.Client/GraphQLHttpClient.cs b/src/GraphQL.Client/GraphQLHttpClient.cs index 3ecb12b1..f4c62977 100644 --- a/src/GraphQL.Client/GraphQLHttpClient.cs +++ b/src/GraphQL.Client/GraphQLHttpClient.cs @@ -188,7 +188,8 @@ protected virtual void Dispose(bool disposing) Debug.WriteLine($"Disposing GraphQLHttpClient on endpoint {Options.EndPoint}"); _cancellationTokenSource.Cancel(); HttpClient.Dispose(); - if(_lazyHttpWebSocket?.HasValue) _lazyHttpWebSocket?.Value.Dispose(); + if ( (bool)(_lazyHttpWebSocket?.IsValueCreated) ) + _lazyHttpWebSocket?.Value.Dispose(); _cancellationTokenSource.Dispose(); } } From e11d0674f59162bd2f65c0f5ddbbf61b810b9366 Mon Sep 17 00:00:00 2001 From: Colony5 <53336327+Maydayof@users.noreply.github.com> Date: Mon, 22 Jun 2020 15:34:57 +0200 Subject: [PATCH 300/455] _lazyHttpWebSocket is not null --- src/GraphQL.Client/GraphQLHttpClient.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/GraphQL.Client/GraphQLHttpClient.cs b/src/GraphQL.Client/GraphQLHttpClient.cs index f4c62977..ea828ec1 100644 --- a/src/GraphQL.Client/GraphQLHttpClient.cs +++ b/src/GraphQL.Client/GraphQLHttpClient.cs @@ -188,8 +188,8 @@ protected virtual void Dispose(bool disposing) Debug.WriteLine($"Disposing GraphQLHttpClient on endpoint {Options.EndPoint}"); _cancellationTokenSource.Cancel(); HttpClient.Dispose(); - if ( (bool)(_lazyHttpWebSocket?.IsValueCreated) ) - _lazyHttpWebSocket?.Value.Dispose(); + if ( _lazyHttpWebSocket.IsValueCreated ) + _lazyHttpWebSocket.Value.Dispose(); _cancellationTokenSource.Dispose(); } } From e0000105b070acacce0866291217ec70705abb8c Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Sat, 15 Aug 2020 10:32:19 +0200 Subject: [PATCH 301/455] add data provided by bjorg to deserializaiton test --- .../TestData/DeserializeResponseTestData.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/GraphQL.Client.Serializer.Tests/TestData/DeserializeResponseTestData.cs b/tests/GraphQL.Client.Serializer.Tests/TestData/DeserializeResponseTestData.cs index 5f1dcdf5..31d02eed 100644 --- a/tests/GraphQL.Client.Serializer.Tests/TestData/DeserializeResponseTestData.cs +++ b/tests/GraphQL.Client.Serializer.Tests/TestData/DeserializeResponseTestData.cs @@ -99,6 +99,21 @@ public IEnumerator GetEnumerator() } }) }; + + // add test for github issue #230 : https://github.com/graphql-dotnet/graphql-client/issues/230 + yield return new object[] { + "{\"data\":{\"getMyModelType\":{\"id\":\"foo\",\"title\":\"The best Foo movie!\"}}}", + new GraphQLResponse { + Data = new GetMyModelTypeResponse + { + getMyModelType = new Movie + { + id = "foo", + title = "The best Foo movie!" + } + }, + } + }; } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); @@ -112,4 +127,16 @@ public class Friend public string Id { get; set; } public string? Name { get; set; } } + + public class GetMyModelTypeResponse + { + //--- Properties --- + public Movie getMyModelType { get; set; } + } + public class Movie + { + //--- Properties --- + public string id { get; set; } + public string title { get; set; } + } } From 73ce1f2377b547f27ca5352cd09375831473d564 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Sat, 15 Aug 2020 11:30:30 +0200 Subject: [PATCH 302/455] migitate issue by using string.IsNullOrEmpty instead of null comparison --- .../ImmutableConverter.cs | 46 ++++++++----------- 1 file changed, 18 insertions(+), 28 deletions(-) diff --git a/src/GraphQL.Client.Serializer.SystemTextJson/ImmutableConverter.cs b/src/GraphQL.Client.Serializer.SystemTextJson/ImmutableConverter.cs index fa0a3f06..7355970d 100644 --- a/src/GraphQL.Client.Serializer.SystemTextJson/ImmutableConverter.cs +++ b/src/GraphQL.Client.Serializer.SystemTextJson/ImmutableConverter.cs @@ -32,21 +32,13 @@ public override bool CanConvert(Type typeToConvert) { var constructor = constructors[0]; var parameters = constructor.GetParameters(); - var hasParameters = parameters.Length > 0; - if (hasParameters) + + if (parameters.Length > 0) { var properties = typeToConvert.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); - result = true; - foreach (var parameter in parameters) - { - var hasMatchingProperty = properties.Any(p => - NameOfPropertyAndParameter.Matches(p.Name, parameter.Name, typeToConvert.IsAnonymous())); - if (!hasMatchingProperty) - { - result = false; - break; - } - } + result = parameters + .Select(parameter => properties.Any(p => NameOfPropertyAndParameter.Matches(p.Name, parameter.Name, typeToConvert.IsAnonymous()))) + .All(hasMatchingProperty => hasMatchingProperty); } else { @@ -69,8 +61,8 @@ public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonS break; } - var jsonPropName = reader.GetString(); - var normalizedPropName = ConvertAndNormalizeName(jsonPropName, options); + string jsonPropName = reader.GetString(); + string normalizedPropName = ConvertAndNormalizeName(jsonPropName, options); if (!namedPropertiesMapping.TryGetValue(normalizedPropName, out var obProp)) { reader.Read(); @@ -86,7 +78,7 @@ public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonS var ctor = typeToConvert.GetConstructors(BindingFlags.Public | BindingFlags.Instance).First(); var parameters = ctor.GetParameters(); var parameterValues = new object[parameters.Length]; - for (var index = 0; index < parameters.Length; index++) + for (int index = 0; index < parameters.Length; index++) { var parameterInfo = parameters[index]; var value = valueOfProperty.First(prop => @@ -142,7 +134,7 @@ private static IReadOnlyDictionary GetNamedProperties(Json name = options.PropertyNamingPolicy?.ConvertName(property.Name) ?? property.Name; } - var normalizedName = NormalizeName(name, options); + string normalizedName = NormalizeName(name, options); result.Add(normalizedName, property); } @@ -151,8 +143,8 @@ private static IReadOnlyDictionary GetNamedProperties(Json private static string ConvertAndNormalizeName(string name, JsonSerializerOptions options) { - var convertedName = options.PropertyNamingPolicy?.ConvertName(name) ?? name; - return options.PropertyNameCaseInsensitive ? convertedName.ToLowerInvariant() : convertedName; + string convertedName = options.PropertyNamingPolicy?.ConvertName(name) ?? name; + return NormalizeName(convertedName, options); } private static string NormalizeName(string name, JsonSerializerOptions options) => options.PropertyNameCaseInsensitive ? name.ToLowerInvariant() : name; @@ -162,12 +154,12 @@ internal static class NameOfPropertyAndParameter { public static bool Matches(string propertyName, string parameterName, bool anonymousType) { - if (propertyName is null && parameterName is null) + if (string.IsNullOrEmpty(propertyName)) { - return true; + return string.IsNullOrEmpty(parameterName); } - if (propertyName is null || parameterName is null) + if (string.IsNullOrEmpty(parameterName)) { return false; } @@ -176,12 +168,10 @@ public static bool Matches(string propertyName, string parameterName, bool anony { return propertyName.Equals(parameterName, StringComparison.Ordinal); } - else - { - var xRight = propertyName.AsSpan(1); - var yRight = parameterName.AsSpan(1); - return char.ToLowerInvariant(propertyName[0]).CompareTo(parameterName[0]) == 0 && xRight.Equals(yRight, StringComparison.Ordinal); - } + + var xRight = propertyName.AsSpan(1); + var yRight = parameterName.AsSpan(1); + return char.ToLowerInvariant(propertyName[0]).CompareTo(parameterName[0]) == 0 && xRight.Equals(yRight, StringComparison.Ordinal); } } From a9c7a346f09042f90fbe2339ce4f9f6037224bc3 Mon Sep 17 00:00:00 2001 From: Eilon Lipton Date: Mon, 17 Aug 2020 16:35:59 -0700 Subject: [PATCH 303/455] Update README.md Co-authored-by: Ivan Maximov --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1e787251..274e92e4 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](https://facebook.github.io/graphql/June2018) +* [GraphQL Specification](https://spec.graphql.org/June2018/) * [GraphQL HomePage](https://graphql.org/learn) ## Usage: From 3fa088ab614dda2d7223ddd89a0335f89106101d Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Mon, 24 Aug 2020 09:59:47 +0200 Subject: [PATCH 304/455] remove eventloop scheduler from send subscription --- src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs index 652e3f77..de25ed28 100644 --- a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs +++ b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs @@ -84,8 +84,8 @@ public GraphQLHttpWebSocket(Uri webSocketUri, GraphQLHttpClient client) Debug.WriteLine($"receive loop scheduler thread id: {Thread.CurrentThread.ManagedThreadId}")); _requestSubscription = _requestSubject - .ObserveOn(_sendLoopScheduler) - .SelectMany(SendWebSocketRequestAsync) + .Select(SendWebSocketRequestAsync) + .Concat() .Subscribe(); } From 8c763ad9b4e8d9405d5556495ed51ef0486ae12e Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Mon, 24 Aug 2020 10:19:40 +0200 Subject: [PATCH 305/455] ignore nuget dir --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index be040ad5..5c6b0a7c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ bin/ obj/ *.user +nuget/ \ No newline at end of file From 9e4e2d71067d98ccba01eafcce6d17a821084dfa Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Mon, 24 Aug 2020 10:40:27 +0200 Subject: [PATCH 306/455] remove all reactive schedulers --- .../Websocket/GraphQLHttpWebSocket.cs | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs index de25ed28..d60d35fd 100644 --- a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs +++ b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs @@ -30,8 +30,6 @@ internal class GraphQLHttpWebSocket : IDisposable private readonly BehaviorSubject _stateSubject = new BehaviorSubject(GraphQLWebsocketConnectionState.Disconnected); private readonly IDisposable _requestSubscription; - private readonly EventLoopScheduler _receiveLoopScheduler = new EventLoopScheduler(); - private readonly EventLoopScheduler _sendLoopScheduler = new EventLoopScheduler(); private int _connectionAttempt = 0; private IConnectableObservable _incomingMessages; @@ -80,8 +78,6 @@ public GraphQLHttpWebSocket(Uri webSocketUri, GraphQLHttpClient client) _client = client; _buffer = new ArraySegment(new byte[8192]); IncomingMessageStream = GetMessageStream(); - _receiveLoopScheduler.Schedule(() => - Debug.WriteLine($"receive loop scheduler thread id: {Thread.CurrentThread.ManagedThreadId}")); _requestSubscription = _requestSubject .Select(SendWebSocketRequestAsync) @@ -436,7 +432,7 @@ private async Task ConnectAsync(CancellationToken token) // create receiving observable _incomingMessages = Observable - .Defer(() => GetReceiveTask().ToObservable().ObserveOn(_receiveLoopScheduler)) + .Defer(() => GetReceiveTask().ToObservable()) .Repeat() // complete sequence on OperationCanceledException, this is triggered by the cancellation token on disposal .Catch(exception => Observable.Empty()) @@ -489,13 +485,13 @@ private Task BackOff() } private IObservable GetMessageStream() => - Observable.Using(() => new EventLoopScheduler(), scheduler => - Observable.Create(async observer => + Observable.Create(async observer => { // make sure the websocket is connected await InitializeWebSocket(); // subscribe observer to message stream - var subscription = new CompositeDisposable(_incomingMessages.ObserveOn(scheduler).Subscribe(observer)) + var subscription = new CompositeDisposable(_incomingMessages + .Subscribe(observer)) { // register the observer's OnCompleted method with the cancellation token to complete the sequence on disposal _internalCancellationTokenSource.Token.Register(observer.OnCompleted) @@ -507,7 +503,7 @@ private IObservable GetMessageStream() => Debug.WriteLine($"new incoming message subscription {hashCode} created"); return subscription; - })); + }); private Task _receiveAsyncTask = null; private readonly object _receiveTaskLocker = new object(); @@ -634,10 +630,7 @@ private async Task CompleteAsync() _exceptionSubject?.OnCompleted(); _exceptionSubject?.Dispose(); _internalCancellationTokenSource.Dispose(); - - _sendLoopScheduler?.Dispose(); - _receiveLoopScheduler?.Dispose(); - + Debug.WriteLine("GraphQLHttpWebSocket disposed"); } From bcbb26f8dd99e2d3fcd489eccc2f964af3384917 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Mon, 24 Aug 2020 15:42:10 +0200 Subject: [PATCH 307/455] adapt test to create 2 subscriptions as simultaneously as possible --- .../Websocket/GraphQLHttpWebSocket.cs | 1 - .../WebsocketTests/Base.cs | 23 +++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs index d60d35fd..b61ab9b9 100644 --- a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs +++ b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs @@ -4,7 +4,6 @@ using System.Net.Http; using System.Net.WebSockets; using System.Reactive; -using System.Reactive.Concurrency; using System.Reactive.Disposables; using System.Reactive.Linq; using System.Reactive.Subjects; diff --git a/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs b/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs index 00b884d3..51857ea2 100644 --- a/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs +++ b/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Net.WebSockets; @@ -257,8 +258,26 @@ public async void CanConnectTwoSubscriptionsSimultaneously() var observable2 = ChatClient.CreateSubscriptionStream(_subscriptionRequest2, callbackTester2.Invoke); Debug.WriteLine("subscribing..."); - var messagesMonitor = observable1.Observe(); - var joinedMonitor = observable2.Observe(); + var blocker = new ManualResetEventSlim(false); + FluentTestObserver> messagesMonitor = null; + FluentTestObserver> joinedMonitor = null; + + var tasks = new List + { + Task.Run(() => + { + blocker.Wait(); + messagesMonitor = observable1.Observe(); + }), + Task.Run(() => + { + blocker.Wait(); + joinedMonitor = observable2.Observe(); + }) + }; + + blocker.Set(); + await Task.WhenAll(tasks); await messagesMonitor.Should().PushAsync(1); messagesMonitor.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(InitialMessage.Content); From f6dc549f3fd0ec304fd10f56334dd24b308608c3 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Thu, 27 Aug 2020 08:55:02 +0200 Subject: [PATCH 308/455] invoke SendWebSocketRequestAsync with Observable.FromAsync --- src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs index b61ab9b9..663a7f0d 100644 --- a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs +++ b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs @@ -79,7 +79,7 @@ public GraphQLHttpWebSocket(Uri webSocketUri, GraphQLHttpClient client) IncomingMessageStream = GetMessageStream(); _requestSubscription = _requestSubject - .Select(SendWebSocketRequestAsync) + .Select(request => Observable.FromAsync(() => SendWebSocketRequestAsync(request))) .Concat() .Subscribe(); } From 3f17623da2487ce3b924af5893dbd153bab657a0 Mon Sep 17 00:00:00 2001 From: "Steve G. Bjorg" Date: Sun, 6 Sep 2020 01:02:25 -0700 Subject: [PATCH 309/455] added try-catch handlers for unsupported properties --- .../Websocket/GraphQLHttpWebSocket.cs | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs index 663a7f0d..66b6edd3 100644 --- a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs +++ b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs @@ -407,8 +407,22 @@ public Task InitializeWebSocket() #else _clientWebSocket = new ClientWebSocket(); _clientWebSocket.Options.AddSubProtocol("graphql-ws"); - _clientWebSocket.Options.ClientCertificates = ((HttpClientHandler)Options.HttpMessageHandler).ClientCertificates; - _clientWebSocket.Options.UseDefaultCredentials = ((HttpClientHandler)Options.HttpMessageHandler).UseDefaultCredentials; + try + { + _clientWebSocket.Options.ClientCertificates = ((HttpClientHandler)Options.HttpMessageHandler).ClientCertificates; + } + catch(PlatformNotSupportedException) + { + Debug.WriteLine("unable to set Options.ClientCertificates property; platform does not support it"); + } + try + { + _clientWebSocket.Options.UseDefaultCredentials = ((HttpClientHandler)Options.HttpMessageHandler).UseDefaultCredentials; + } + catch(PlatformNotSupportedException) + { + Debug.WriteLine("unable to set Options.UseDefaultCredentials property; platform does not support it"); + } Options.ConfigureWebsocketOptions(_clientWebSocket.Options); #endif return _initializeWebSocketTask = ConnectAsync(_internalCancellationToken); @@ -605,7 +619,7 @@ public void Complete() /// /// Task to await the completion (a.k.a. disposal) of this websocket. - /// + /// /// Async disposal as recommended by Stephen Cleary (https://blog.stephencleary.com/2013/03/async-oop-6-disposal.html) public Task? Completion { get; private set; } @@ -629,7 +643,7 @@ private async Task CompleteAsync() _exceptionSubject?.OnCompleted(); _exceptionSubject?.Dispose(); _internalCancellationTokenSource.Dispose(); - + Debug.WriteLine("GraphQLHttpWebSocket disposed"); } From 4beeea32ed6f711dcb3d90bfd3762ec754a3f545 Mon Sep 17 00:00:00 2001 From: "Steve G. Bjorg" Date: Sun, 6 Sep 2020 01:08:58 -0700 Subject: [PATCH 310/455] fix whitespace changes --- src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs index 66b6edd3..b64e93aa 100644 --- a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs +++ b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs @@ -619,7 +619,7 @@ public void Complete() /// /// Task to await the completion (a.k.a. disposal) of this websocket. - /// + /// /// Async disposal as recommended by Stephen Cleary (https://blog.stephencleary.com/2013/03/async-oop-6-disposal.html) public Task? Completion { get; private set; } @@ -643,7 +643,7 @@ private async Task CompleteAsync() _exceptionSubject?.OnCompleted(); _exceptionSubject?.Dispose(); _internalCancellationTokenSource.Dispose(); - + Debug.WriteLine("GraphQLHttpWebSocket disposed"); } From d63b1d5fc106b56d7206fc1a1c4936bda29f7c83 Mon Sep 17 00:00:00 2001 From: "Steve G. Bjorg" Date: Tue, 8 Sep 2020 07:48:07 -0700 Subject: [PATCH 311/455] formatting --- src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs index b64e93aa..fec76d34 100644 --- a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs +++ b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs @@ -411,7 +411,7 @@ public Task InitializeWebSocket() { _clientWebSocket.Options.ClientCertificates = ((HttpClientHandler)Options.HttpMessageHandler).ClientCertificates; } - catch(PlatformNotSupportedException) + catch (PlatformNotSupportedException) { Debug.WriteLine("unable to set Options.ClientCertificates property; platform does not support it"); } @@ -419,7 +419,7 @@ public Task InitializeWebSocket() { _clientWebSocket.Options.UseDefaultCredentials = ((HttpClientHandler)Options.HttpMessageHandler).UseDefaultCredentials; } - catch(PlatformNotSupportedException) + catch (PlatformNotSupportedException) { Debug.WriteLine("unable to set Options.UseDefaultCredentials property; platform does not support it"); } From 9ee154377cfdb6a41af6569db398ddd7135e3e72 Mon Sep 17 00:00:00 2001 From: "Steve G. Bjorg" Date: Tue, 8 Sep 2020 12:22:53 -0700 Subject: [PATCH 312/455] allow wss:// scheme, preserve uri query parameters --- src/GraphQL.Client/GraphQLHttpClient.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/GraphQL.Client/GraphQLHttpClient.cs b/src/GraphQL.Client/GraphQLHttpClient.cs index ea828ec1..9b3b86ae 100644 --- a/src/GraphQL.Client/GraphQLHttpClient.cs +++ b/src/GraphQL.Client/GraphQLHttpClient.cs @@ -66,6 +66,10 @@ public GraphQLHttpClient(GraphQLHttpClientOptions options, IGraphQLWebsocketJson HttpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(GetType().Assembly.GetName().Name, GetType().Assembly.GetName().Version.ToString())); _lazyHttpWebSocket = new Lazy(() => new GraphQLHttpWebSocket(GetWebSocketUri(), this)); + if ((Options.EndPoint?.Scheme == "wss") || (Options.EndPoint?.Scheme == "ws")) + { + Options.UseWebSocketForQueriesAndMutations = true; + } } #endregion @@ -155,8 +159,12 @@ private async Task> SendHttpRequestAsync Date: Tue, 8 Sep 2020 12:37:36 -0700 Subject: [PATCH 313/455] Revert "allow wss:// scheme, preserve uri query parameters" This reverts commit 9ee154377cfdb6a41af6569db398ddd7135e3e72. --- src/GraphQL.Client/GraphQLHttpClient.cs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/GraphQL.Client/GraphQLHttpClient.cs b/src/GraphQL.Client/GraphQLHttpClient.cs index 9b3b86ae..ea828ec1 100644 --- a/src/GraphQL.Client/GraphQLHttpClient.cs +++ b/src/GraphQL.Client/GraphQLHttpClient.cs @@ -66,10 +66,6 @@ public GraphQLHttpClient(GraphQLHttpClientOptions options, IGraphQLWebsocketJson HttpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(GetType().Assembly.GetName().Name, GetType().Assembly.GetName().Version.ToString())); _lazyHttpWebSocket = new Lazy(() => new GraphQLHttpWebSocket(GetWebSocketUri(), this)); - if ((Options.EndPoint?.Scheme == "wss") || (Options.EndPoint?.Scheme == "ws")) - { - Options.UseWebSocketForQueriesAndMutations = true; - } } #endregion @@ -159,12 +155,8 @@ private async Task> SendHttpRequestAsync Date: Tue, 8 Sep 2020 20:10:03 -0700 Subject: [PATCH 314/455] added readme note --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 274e92e4..32d35da0 100644 --- a/README.md +++ b/README.md @@ -139,3 +139,9 @@ subscription.Dispose(); * [GitHub GraphQL API Docs](https://developer.github.com/v4/guides/forming-calls/) * [GitHub GraphQL Explorer](https://developer.github.com/v4/explorer/) * [GitHub GraphQL Endpoint](https://api.github.com/graphql) + +## Blazor WebAssembly Limitations + +Blazor WebAssembly differs from other platforms as it does not support all features of other .NET runtime implementations. For instance, the following WebSocket options properties are not supported and will not be set: +* [ClientCertificates](https://docs.microsoft.com/en-us/dotnet/api/system.net.websockets.clientwebsocketoptions.clientcertificates?view=netcore-3.1#System_Net_WebSockets_ClientWebSocketOptions_ClientCertificates) +* [UseDefaultCredentials](https://docs.microsoft.com/en-us/dotnet/api/system.net.websockets.clientwebsocketoptions.usedefaultcredentials?view=netcore-3.1) From 94a1e32520fc20d7829aecfc2d4ca5f97456f62e Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Tue, 15 Sep 2020 22:02:34 +0200 Subject: [PATCH 315/455] handle WebSocket url scheme --- src/GraphQL.Client/GraphQLHttpClient.cs | 15 ++++--------- src/GraphQL.Client/UriExtensions.cs | 28 +++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 11 deletions(-) create mode 100644 src/GraphQL.Client/UriExtensions.cs diff --git a/src/GraphQL.Client/GraphQLHttpClient.cs b/src/GraphQL.Client/GraphQLHttpClient.cs index ea828ec1..21485f01 100644 --- a/src/GraphQL.Client/GraphQLHttpClient.cs +++ b/src/GraphQL.Client/GraphQLHttpClient.cs @@ -55,7 +55,7 @@ public GraphQLHttpClient(Uri endPoint, IGraphQLWebsocketJsonSerializer serialize public GraphQLHttpClient(Action configure, IGraphQLWebsocketJsonSerializer serializer) : this(configure.New(), serializer) { } public GraphQLHttpClient(GraphQLHttpClientOptions options, IGraphQLWebsocketJsonSerializer serializer) : this(options, serializer, new HttpClient(options.HttpMessageHandler)) { } - + public GraphQLHttpClient(GraphQLHttpClientOptions options, IGraphQLWebsocketJsonSerializer serializer, HttpClient httpClient) { Options = options ?? throw new ArgumentNullException(nameof(options)); @@ -65,7 +65,7 @@ public GraphQLHttpClient(GraphQLHttpClientOptions options, IGraphQLWebsocketJson if (!HttpClient.DefaultRequestHeaders.UserAgent.Any()) HttpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(GetType().Assembly.GetName().Name, GetType().Assembly.GetName().Version.ToString())); - _lazyHttpWebSocket = new Lazy(() => new GraphQLHttpWebSocket(GetWebSocketUri(), this)); + _lazyHttpWebSocket = new Lazy(() => new GraphQLHttpWebSocket(Options.EndPoint.GetWebSocketUri(), this)); } #endregion @@ -75,7 +75,7 @@ public GraphQLHttpClient(GraphQLHttpClientOptions options, IGraphQLWebsocketJson /// public async Task> SendQueryAsync(GraphQLRequest request, CancellationToken cancellationToken = default) { - if (Options.UseWebSocketForQueriesAndMutations) + if (Options.UseWebSocketForQueriesAndMutations || Options.EndPoint.HasWebSocketScheme()) return await _graphQlHttpWebSocket.SendRequest(request, cancellationToken); return await SendHttpRequestAsync(request, cancellationToken); @@ -132,7 +132,7 @@ public IObservable> CreateSubscriptionStream> SendHttpRequestAsync(GraphQLRequest request, CancellationToken cancellationToken = default) { var preprocessedRequest = await Options.PreprocessRequest(request, this); - + using var httpRequestMessage = preprocessedRequest.ToHttpRequestMessage(Options, JsonSerializer); using var httpResponseMessage = await HttpClient.SendAsync(httpRequestMessage, HttpCompletionOption.ResponseHeadersRead, cancellationToken); @@ -152,13 +152,6 @@ private async Task> SendHttpRequestAsync + /// Returns true if equals "wss" or "ws" + /// + /// + /// + public static bool HasWebSocketScheme(this Uri uri) => uri.Scheme.Equals("wss") || uri.Scheme.Equals("ws"); + + /// + /// Infers the websocket uri from . + /// + /// + /// + public static Uri GetWebSocketUri(this Uri uri) + { + if (uri.HasWebSocketScheme()) + return uri; + + string webSocketScheme = uri.Scheme == "https" ? "wss" : "ws"; + return new Uri($"{webSocketScheme}://{uri.Host}:{uri.Port}{uri.AbsolutePath}"); + } + } +} From b5b036583f89a728ed6cfa75c480881545956cb6 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Tue, 15 Sep 2020 22:13:30 +0200 Subject: [PATCH 316/455] add test for websocket scheme --- tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs b/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs index 51857ea2..4f90c1a5 100644 --- a/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs +++ b/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs @@ -70,6 +70,16 @@ public async void CanSendRequestViaWebsocket() response.Data.AddMessage.Content.Should().Be(message); } + [Fact] + public async void CanUseWebSocketScheme() + { + ChatClient.Options.EndPoint = ChatClient.Options.EndPoint.GetWebSocketUri(); + await ChatClient.InitializeWebsocketConnection(); + const string message = "some random testing message"; + var response = await ChatClient.AddMessageAsync(message); + response.Data.AddMessage.Content.Should().Be(message); + } + [Fact] public async void WebsocketRequestCanBeCancelled() { From fa944ec4f417ef2ad60e78be4f8b1c3505ecf39b Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Tue, 15 Sep 2020 22:22:58 +0200 Subject: [PATCH 317/455] append query in GetWebSocketUri() --- src/GraphQL.Client/UriExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GraphQL.Client/UriExtensions.cs b/src/GraphQL.Client/UriExtensions.cs index 77156a4b..a3ab2b04 100644 --- a/src/GraphQL.Client/UriExtensions.cs +++ b/src/GraphQL.Client/UriExtensions.cs @@ -22,7 +22,7 @@ public static Uri GetWebSocketUri(this Uri uri) return uri; string webSocketScheme = uri.Scheme == "https" ? "wss" : "ws"; - return new Uri($"{webSocketScheme}://{uri.Host}:{uri.Port}{uri.AbsolutePath}"); + return new Uri($"{webSocketScheme}://{uri.Host}:{uri.Port}{uri.PathAndQuery}"); } } } From c62301b4bc9b7075fab2c7f702e1bbd7788b417d Mon Sep 17 00:00:00 2001 From: Dimitrie Stefanescu Date: Sat, 29 Aug 2020 11:49:28 +0100 Subject: [PATCH 318/455] fix(ws): added switch statement to handle close message events --- .../Websocket/GraphQLHttpWebSocket.cs | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs index fec76d34..da9e8a9e 100644 --- a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs +++ b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs @@ -114,6 +114,7 @@ public IObservable> CreateSubscriptionStream>(o => @@ -563,16 +564,22 @@ private async Task ReceiveWebsocketMessagesAsync() _internalCancellationToken.ThrowIfCancellationRequested(); ms.Seek(0, SeekOrigin.Begin); - if (webSocketReceiveResult.MessageType == WebSocketMessageType.Text) + switch (webSocketReceiveResult.MessageType) { - var response = await _client.JsonSerializer.DeserializeToWebsocketResponseWrapperAsync(ms); - response.MessageBytes = ms.ToArray(); - Debug.WriteLine($"{response.MessageBytes.Length} bytes received for id {response.Id} on websocket {_clientWebSocket.GetHashCode()} (thread {Thread.CurrentThread.ManagedThreadId})..."); - return response; - } - else - { - throw new NotSupportedException("binary websocket messages are not supported"); + case WebSocketMessageType.Text: + var response = await _client.JsonSerializer.DeserializeToWebsocketResponseWrapperAsync(ms); + response.MessageBytes = ms.ToArray(); + Debug.WriteLine($"{response.MessageBytes.Length} bytes received for id {response.Id} on websocket {_clientWebSocket.GetHashCode()} (thread {Thread.CurrentThread.ManagedThreadId})..."); + return response; + + case WebSocketMessageType.Close: + var closeResponse = await _client.JsonSerializer.DeserializeToWebsocketResponseWrapperAsync(ms); + closeResponse.MessageBytes = ms.ToArray(); + Debug.WriteLine($"Connection closed by the server."); + throw new Exception("Connection closed by the server."); + + case WebSocketMessageType.Binary: + throw new NotSupportedException("binary websocket messages are not supported"); } } catch (Exception e) @@ -643,7 +650,7 @@ private async Task CompleteAsync() _exceptionSubject?.OnCompleted(); _exceptionSubject?.Dispose(); _internalCancellationTokenSource.Dispose(); - + Debug.WriteLine("GraphQLHttpWebSocket disposed"); } From 15c5e3668d9e6c91192e8cccbcad0a8ee5e47b99 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Tue, 15 Sep 2020 22:40:40 +0200 Subject: [PATCH 319/455] cherrypick webSocketReceiveResult default case from pr 272 --- src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs index da9e8a9e..2f4a86f4 100644 --- a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs +++ b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs @@ -578,8 +578,9 @@ private async Task ReceiveWebsocketMessagesAsync() Debug.WriteLine($"Connection closed by the server."); throw new Exception("Connection closed by the server."); - case WebSocketMessageType.Binary: - throw new NotSupportedException("binary websocket messages are not supported"); + default: + throw new NotSupportedException($"Websocket message type {webSocketReceiveResult.MessageType} not supported."); + } } catch (Exception e) From 501ba3c26c2a1c9cc13d930bfacd4588e53a0b60 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Wed, 16 Sep 2020 20:39:26 +0200 Subject: [PATCH 320/455] fix uri conversion and add some tests to prove its working --- src/GraphQL.Client/UriExtensions.cs | 14 ++++-- .../UriExtensionTests.cs | 47 +++++++++++++++++++ 2 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 tests/GraphQL.Integration.Tests/UriExtensionTests.cs diff --git a/src/GraphQL.Client/UriExtensions.cs b/src/GraphQL.Client/UriExtensions.cs index a3ab2b04..ad40a5bf 100644 --- a/src/GraphQL.Client/UriExtensions.cs +++ b/src/GraphQL.Client/UriExtensions.cs @@ -9,7 +9,7 @@ public static class UriExtensions /// /// /// - public static bool HasWebSocketScheme(this Uri uri) => uri.Scheme.Equals("wss") || uri.Scheme.Equals("ws"); + public static bool HasWebSocketScheme(this Uri uri) => uri.Scheme.Equals("wss", StringComparison.OrdinalIgnoreCase) || uri.Scheme.Equals("ws", StringComparison.OrdinalIgnoreCase); /// /// Infers the websocket uri from . @@ -21,8 +21,16 @@ public static Uri GetWebSocketUri(this Uri uri) if (uri.HasWebSocketScheme()) return uri; - string webSocketScheme = uri.Scheme == "https" ? "wss" : "ws"; - return new Uri($"{webSocketScheme}://{uri.Host}:{uri.Port}{uri.PathAndQuery}"); + string webSocketScheme; + + if (uri.Scheme == Uri.UriSchemeHttps) + webSocketScheme = "wss"; + else if (uri.Scheme == Uri.UriSchemeHttp) + webSocketScheme = "ws"; + else + throw new NotSupportedException($"cannot infer websocket uri from uri scheme {uri.Scheme}"); + + return new UriBuilder(uri){Scheme = webSocketScheme}.Uri; } } } diff --git a/tests/GraphQL.Integration.Tests/UriExtensionTests.cs b/tests/GraphQL.Integration.Tests/UriExtensionTests.cs new file mode 100644 index 00000000..29c98c6a --- /dev/null +++ b/tests/GraphQL.Integration.Tests/UriExtensionTests.cs @@ -0,0 +1,47 @@ +using System; +using FluentAssertions; +using GraphQL.Client.Http; +using Xunit; + +namespace GraphQL.Integration.Tests +{ + public class UriExtensionTests + { + [Theory] + [InlineData("http://thats-not-a-websocket-url.net", false)] + [InlineData("https://thats-not-a-websocket-url.net", false)] + [InlineData("ftp://thats-not-a-websocket-url.net", false)] + [InlineData("ws://that-is-a-websocket-url.net", true)] + [InlineData("wss://that-is-a-websocket-url.net", true)] + [InlineData("WS://that-is-a-websocket-url.net", true)] + [InlineData("WSS://that-is-a-websocket-url.net", true)] + public void HasWebSocketSchemaTest(string url, bool result) + { + new Uri(url).HasWebSocketScheme().Should().Be(result); + } + + [Theory] + [InlineData("http://this-url-can-be-converted.net", true, "ws://this-url-can-be-converted.net")] + [InlineData("https://this-url-can-be-converted.net", true, "wss://this-url-can-be-converted.net")] + [InlineData("HTTP://this-url-can-be-converted.net", true, "ws://this-url-can-be-converted.net")] + [InlineData("HTTPS://this-url-can-be-converted.net", true, "wss://this-url-can-be-converted.net")] + [InlineData("ws://this-url-can-be-converted.net", true, "ws://this-url-can-be-converted.net")] + [InlineData("wss://this-url-can-be-converted.net", true, "wss://this-url-can-be-converted.net")] + [InlineData("https://this-url-can-be-converted.net/and/all/elements/?are#preserved", true, "wss://this-url-can-be-converted.net/and/all/elements/?are#preserved")] + [InlineData("ftp://this-url-can-be-converted.net", false, null)] + // AppSync example + [InlineData("wss://example1234567890000.appsync-realtime-api.us-west-2.amazonaws.com/graphql?header=123456789ABCDEF&payload=e30=", true, "wss://example1234567890000.appsync-realtime-api.us-west-2.amazonaws.com/graphql?header=123456789ABCDEF&payload=e30=")] + public void GetWebSocketUriTest(string input, bool canConvert, string result) + { + var inputUri = new Uri(input); + if (canConvert) + { + inputUri.GetWebSocketUri().Should().BeEquivalentTo(new Uri(result)); + } + else + { + inputUri.Invoking(uri => uri.GetWebSocketUri()).Should().Throw(); + } + } + } +} From 01367df92c0b01e68713086880dc84e41ec6a472 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Wed, 16 Sep 2020 20:43:56 +0200 Subject: [PATCH 321/455] fix uri in test --- tests/GraphQL.Integration.Tests/UriExtensionTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/GraphQL.Integration.Tests/UriExtensionTests.cs b/tests/GraphQL.Integration.Tests/UriExtensionTests.cs index 29c98c6a..1298ca11 100644 --- a/tests/GraphQL.Integration.Tests/UriExtensionTests.cs +++ b/tests/GraphQL.Integration.Tests/UriExtensionTests.cs @@ -28,7 +28,7 @@ public void HasWebSocketSchemaTest(string url, bool result) [InlineData("ws://this-url-can-be-converted.net", true, "ws://this-url-can-be-converted.net")] [InlineData("wss://this-url-can-be-converted.net", true, "wss://this-url-can-be-converted.net")] [InlineData("https://this-url-can-be-converted.net/and/all/elements/?are#preserved", true, "wss://this-url-can-be-converted.net/and/all/elements/?are#preserved")] - [InlineData("ftp://this-url-can-be-converted.net", false, null)] + [InlineData("ftp://this-url-cannot-be-converted.net", false, null)] // AppSync example [InlineData("wss://example1234567890000.appsync-realtime-api.us-west-2.amazonaws.com/graphql?header=123456789ABCDEF&payload=e30=", true, "wss://example1234567890000.appsync-realtime-api.us-west-2.amazonaws.com/graphql?header=123456789ABCDEF&payload=e30=")] public void GetWebSocketUriTest(string input, bool canConvert, string result) From a80c995e5e63cd54b2843e0aea88856f63621de4 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Wed, 16 Sep 2020 22:05:16 +0200 Subject: [PATCH 322/455] add constant case enum converters for both serializers --- .../GraphQL.Client.LocalExecution.csproj | 1 + .../GraphQLLocalExecutionClient.cs | 7 ++++--- .../ConstantCaseEnumConverter.cs} | 8 ++++---- .../GraphQL.Client.Serializer.Newtonsoft.csproj | 1 + .../NewtonsoftJsonSerializer.cs | 3 ++- .../ConstantCaseJsonNamingPolicy.cs | 10 ++++++++++ ...aphQL.Client.Serializer.SystemTextJson.csproj | 4 ++++ .../SystemTextJsonSerializer.cs | 4 +++- .../NewtonsoftSerializerTest.cs | 2 +- .../SystemTextJsonSerializerTests.cs | 3 ++- .../TestData/SerializeToStringTestData.cs | 16 ++++++++++++++++ 11 files changed, 48 insertions(+), 11 deletions(-) rename src/{GraphQL.Client.LocalExecution/GraphQLEnumConverter.cs => GraphQL.Client.Serializer.Newtonsoft/ConstantCaseEnumConverter.cs} (82%) create mode 100644 src/GraphQL.Client.Serializer.SystemTextJson/ConstantCaseJsonNamingPolicy.cs diff --git a/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj b/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj index ebef7ea0..e8e162cb 100644 --- a/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj +++ b/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj @@ -15,6 +15,7 @@ + diff --git a/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs b/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs index f8fe6503..65098ae8 100644 --- a/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs +++ b/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using GraphQL.Client.Abstractions; +using GraphQL.Client.Serializer.Newtonsoft; using GraphQL.Subscription; using GraphQL.Types; using Newtonsoft.Json; @@ -31,7 +32,7 @@ public class GraphQLLocalExecutionClient : IGraphQLClient where TSchema ContractResolver = new CamelCasePropertyNamesContractResolver(), Converters = new List { - new GraphQLEnumConverter() + new ConstantCaseEnumConverter() } }; @@ -50,7 +51,7 @@ public GraphQLLocalExecutionClient(TSchema schema, IGraphQLJsonSerializer serial Schema.Initialize(); _documentExecuter = new DocumentExecuter(); } - + public void Dispose() { } public Task> SendQueryAsync(GraphQLRequest request, CancellationToken cancellationToken = default) @@ -80,7 +81,7 @@ private async Task>> ExecuteSubscriptionA { var result = await ExecuteAsync(request, cancellationToken); var stream = ((SubscriptionExecutionResult)result).Streams?.Values.SingleOrDefault(); - + return stream == null ? Observable.Throw>(new InvalidOperationException("the GraphQL execution did not return an observable")) : stream.SelectMany(executionResult => Observable.FromAsync(token => ExecutionResultToGraphQLResponse(executionResult, token))); diff --git a/src/GraphQL.Client.LocalExecution/GraphQLEnumConverter.cs b/src/GraphQL.Client.Serializer.Newtonsoft/ConstantCaseEnumConverter.cs similarity index 82% rename from src/GraphQL.Client.LocalExecution/GraphQLEnumConverter.cs rename to src/GraphQL.Client.Serializer.Newtonsoft/ConstantCaseEnumConverter.cs index a02cf935..5eb45f73 100644 --- a/src/GraphQL.Client.LocalExecution/GraphQLEnumConverter.cs +++ b/src/GraphQL.Client.Serializer.Newtonsoft/ConstantCaseEnumConverter.cs @@ -1,13 +1,13 @@ using System; using System.Linq; using System.Reflection; -using GraphQL.Utilities; using Newtonsoft.Json; using Newtonsoft.Json.Converters; +using Panic.StringUtils.Extensions; -namespace GraphQL.Client.LocalExecution +namespace GraphQL.Client.Serializer.Newtonsoft { - public class GraphQLEnumConverter : StringEnumConverter + public class ConstantCaseEnumConverter : StringEnumConverter { public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { @@ -29,7 +29,7 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s } else { - writer.WriteValue(StringUtils.ToConstantCase(memberName)); + writer.WriteValue(memberName.ToConstantCase()); } } } diff --git a/src/GraphQL.Client.Serializer.Newtonsoft/GraphQL.Client.Serializer.Newtonsoft.csproj b/src/GraphQL.Client.Serializer.Newtonsoft/GraphQL.Client.Serializer.Newtonsoft.csproj index 4bbcb784..f603fc93 100644 --- a/src/GraphQL.Client.Serializer.Newtonsoft/GraphQL.Client.Serializer.Newtonsoft.csproj +++ b/src/GraphQL.Client.Serializer.Newtonsoft/GraphQL.Client.Serializer.Newtonsoft.csproj @@ -9,6 +9,7 @@ + diff --git a/src/GraphQL.Client.Serializer.Newtonsoft/NewtonsoftJsonSerializer.cs b/src/GraphQL.Client.Serializer.Newtonsoft/NewtonsoftJsonSerializer.cs index 5ec06c1a..e7cae90f 100644 --- a/src/GraphQL.Client.Serializer.Newtonsoft/NewtonsoftJsonSerializer.cs +++ b/src/GraphQL.Client.Serializer.Newtonsoft/NewtonsoftJsonSerializer.cs @@ -15,7 +15,8 @@ public class NewtonsoftJsonSerializer : IGraphQLWebsocketJsonSerializer public static JsonSerializerSettings DefaultJsonSerializerSettings => new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver { IgnoreIsSpecifiedMembers = true }, - MissingMemberHandling = MissingMemberHandling.Ignore + MissingMemberHandling = MissingMemberHandling.Ignore, + Converters = { new ConstantCaseEnumConverter() } }; public JsonSerializerSettings JsonSerializerSettings { get; } diff --git a/src/GraphQL.Client.Serializer.SystemTextJson/ConstantCaseJsonNamingPolicy.cs b/src/GraphQL.Client.Serializer.SystemTextJson/ConstantCaseJsonNamingPolicy.cs new file mode 100644 index 00000000..b2ea49eb --- /dev/null +++ b/src/GraphQL.Client.Serializer.SystemTextJson/ConstantCaseJsonNamingPolicy.cs @@ -0,0 +1,10 @@ +using System.Text.Json; +using Panic.StringUtils.Extensions; + +namespace GraphQL.Client.Serializer.SystemTextJson +{ + public class ConstantCaseJsonNamingPolicy: JsonNamingPolicy + { + public override string ConvertName(string name) => name.ToConstantCase(); + } +} diff --git a/src/GraphQL.Client.Serializer.SystemTextJson/GraphQL.Client.Serializer.SystemTextJson.csproj b/src/GraphQL.Client.Serializer.SystemTextJson/GraphQL.Client.Serializer.SystemTextJson.csproj index 9433b8dd..93fe58af 100644 --- a/src/GraphQL.Client.Serializer.SystemTextJson/GraphQL.Client.Serializer.SystemTextJson.csproj +++ b/src/GraphQL.Client.Serializer.SystemTextJson/GraphQL.Client.Serializer.SystemTextJson.csproj @@ -15,4 +15,8 @@ + + + + diff --git a/src/GraphQL.Client.Serializer.SystemTextJson/SystemTextJsonSerializer.cs b/src/GraphQL.Client.Serializer.SystemTextJson/SystemTextJsonSerializer.cs index a1667ec8..d0325464 100644 --- a/src/GraphQL.Client.Serializer.SystemTextJson/SystemTextJsonSerializer.cs +++ b/src/GraphQL.Client.Serializer.SystemTextJson/SystemTextJsonSerializer.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Text.Json; +using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; using GraphQL.Client.Abstractions; @@ -12,7 +13,8 @@ public class SystemTextJsonSerializer : IGraphQLWebsocketJsonSerializer { public static JsonSerializerOptions DefaultJsonSerializerOptions => new JsonSerializerOptions { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + Converters = { new JsonStringEnumConverter(new ConstantCaseJsonNamingPolicy(), false)} }.SetupImmutableConverter(); public JsonSerializerOptions Options { get; } diff --git a/tests/GraphQL.Client.Serializer.Tests/NewtonsoftSerializerTest.cs b/tests/GraphQL.Client.Serializer.Tests/NewtonsoftSerializerTest.cs index 00b6d03b..43ade957 100644 --- a/tests/GraphQL.Client.Serializer.Tests/NewtonsoftSerializerTest.cs +++ b/tests/GraphQL.Client.Serializer.Tests/NewtonsoftSerializerTest.cs @@ -11,6 +11,6 @@ public NewtonsoftSerializerTest() : base(new NewtonsoftJsonSerializer()) { } public class NewtonsoftSerializeNoCamelCaseTest : BaseSerializeNoCamelCaseTest { public NewtonsoftSerializeNoCamelCaseTest() - : base(new NewtonsoftJsonSerializer(new JsonSerializerSettings())) { } + : base(new NewtonsoftJsonSerializer(new JsonSerializerSettings(){ Converters = { new ConstantCaseEnumConverter() } })) { } } } diff --git a/tests/GraphQL.Client.Serializer.Tests/SystemTextJsonSerializerTests.cs b/tests/GraphQL.Client.Serializer.Tests/SystemTextJsonSerializerTests.cs index 217c9da1..153bcd4d 100644 --- a/tests/GraphQL.Client.Serializer.Tests/SystemTextJsonSerializerTests.cs +++ b/tests/GraphQL.Client.Serializer.Tests/SystemTextJsonSerializerTests.cs @@ -1,4 +1,5 @@ using System.Text.Json; +using System.Text.Json.Serialization; using GraphQL.Client.Serializer.SystemTextJson; namespace GraphQL.Client.Serializer.Tests @@ -11,6 +12,6 @@ public SystemTextJsonSerializerTests() : base(new SystemTextJsonSerializer()) { public class SystemTextJsonSerializeNoCamelCaseTest : BaseSerializeNoCamelCaseTest { public SystemTextJsonSerializeNoCamelCaseTest() - : base(new SystemTextJsonSerializer(new JsonSerializerOptions().SetupImmutableConverter())) { } + : base(new SystemTextJsonSerializer(new JsonSerializerOptions(){Converters = { new JsonStringEnumConverter(new ConstantCaseJsonNamingPolicy(), false)}}.SetupImmutableConverter())) { } } } diff --git a/tests/GraphQL.Client.Serializer.Tests/TestData/SerializeToStringTestData.cs b/tests/GraphQL.Client.Serializer.Tests/TestData/SerializeToStringTestData.cs index 06208c21..e5285681 100644 --- a/tests/GraphQL.Client.Serializer.Tests/TestData/SerializeToStringTestData.cs +++ b/tests/GraphQL.Client.Serializer.Tests/TestData/SerializeToStringTestData.cs @@ -1,5 +1,7 @@ +using System; using System.Collections; using System.Collections.Generic; +using System.Linq; namespace GraphQL.Client.Serializer.Tests.TestData { @@ -19,8 +21,22 @@ public IEnumerator GetEnumerator() "{\"query\":\"simplequerystring\",\"variables\":null,\"operationName\":null,\"authentication\":\"an-authentication-token\"}", new GraphQLRequest("simple query string"){{"authentication", "an-authentication-token"}} }; + yield return new object[] { + "{\"query\":\"enumtest\",\"variables\":{\"enums\":[\"REGULAR\",\"PASCAL_CASE\",\"CAMEL_CASE\",\"LOWER\",\"UPPER\",\"CONSTANT_CASE\"]},\"operationName\":null}", + new GraphQLRequest("enumtest", new { enums = Enum.GetValues(typeof(TestEnum)).Cast()}) + }; } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public enum TestEnum + { + Regular, + PascalCase, + camelCase, + lower, + UPPER, + CONSTANT_CASE + } } } From 7fe7137ff9715254be083115e29685d4c9f1aabd Mon Sep 17 00:00:00 2001 From: Steve Bjorg Date: Thu, 17 Sep 2020 08:54:24 -0700 Subject: [PATCH 323/455] check if running in WebAssembly instead of catching PlatformNotSupportedException --- .../Websocket/GraphQLHttpWebSocket.cs | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs index 2f4a86f4..aba35e8f 100644 --- a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs +++ b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs @@ -408,22 +408,12 @@ public Task InitializeWebSocket() #else _clientWebSocket = new ClientWebSocket(); _clientWebSocket.Options.AddSubProtocol("graphql-ws"); - try + if(!System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Create("WEBASSEMBLY"))) { + // the following properties are not supported in Blazor WebAssembly and throw a PlatformNotSupportedException error when accessed _clientWebSocket.Options.ClientCertificates = ((HttpClientHandler)Options.HttpMessageHandler).ClientCertificates; - } - catch (PlatformNotSupportedException) - { - Debug.WriteLine("unable to set Options.ClientCertificates property; platform does not support it"); - } - try - { _clientWebSocket.Options.UseDefaultCredentials = ((HttpClientHandler)Options.HttpMessageHandler).UseDefaultCredentials; } - catch (PlatformNotSupportedException) - { - Debug.WriteLine("unable to set Options.UseDefaultCredentials property; platform does not support it"); - } Options.ConfigureWebsocketOptions(_clientWebSocket.Options); #endif return _initializeWebSocketTask = ConnectAsync(_internalCancellationToken); @@ -627,7 +617,7 @@ public void Complete() /// /// Task to await the completion (a.k.a. disposal) of this websocket. - /// + /// /// Async disposal as recommended by Stephen Cleary (https://blog.stephencleary.com/2013/03/async-oop-6-disposal.html) public Task? Completion { get; private set; } From c1fc64ac1eed1d9b3d7536c2a699741ce6713baf Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Thu, 17 Sep 2020 19:59:04 +0200 Subject: [PATCH 324/455] remove Panic.StringUtils dependency --- .../GraphQL.Client.Abstractions.csproj | 4 + .../Utilities/StringExtensions.cs | 37 ++++ .../Utilities/StringUtils.cs | 193 ++++++++++++++++++ .../ConstantCaseEnumConverter.cs | 2 +- ...raphQL.Client.Serializer.Newtonsoft.csproj | 1 - .../ConstantCaseJsonNamingPolicy.cs | 2 +- ...QL.Client.Serializer.SystemTextJson.csproj | 4 - 7 files changed, 236 insertions(+), 7 deletions(-) create mode 100644 src/GraphQL.Client.Abstractions/Utilities/StringExtensions.cs create mode 100644 src/GraphQL.Client.Abstractions/Utilities/StringUtils.cs diff --git a/src/GraphQL.Client.Abstractions/GraphQL.Client.Abstractions.csproj b/src/GraphQL.Client.Abstractions/GraphQL.Client.Abstractions.csproj index d7678b66..8e1faee1 100644 --- a/src/GraphQL.Client.Abstractions/GraphQL.Client.Abstractions.csproj +++ b/src/GraphQL.Client.Abstractions/GraphQL.Client.Abstractions.csproj @@ -15,4 +15,8 @@ + + + + diff --git a/src/GraphQL.Client.Abstractions/Utilities/StringExtensions.cs b/src/GraphQL.Client.Abstractions/Utilities/StringExtensions.cs new file mode 100644 index 00000000..38c718a8 --- /dev/null +++ b/src/GraphQL.Client.Abstractions/Utilities/StringExtensions.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; + +namespace GraphQL.Client.Abstractions.Utilities +{ + /// + /// Copied from https://github.com/jquense/StringUtils + /// + public static class StringExtensions + { + public static string StripIndent(this string str) => GraphQL.Client.Abstractions.Utilities.StringUtils.StripIndent(str); + + public static IEnumerable ToWords(this string str) => GraphQL.Client.Abstractions.Utilities.StringUtils.ToWords(str); + + public static string ToUpperFirst(this string str) => GraphQL.Client.Abstractions.Utilities.StringUtils.ToUpperFirst(str); + + public static string ToLowerFirst(this string str) => GraphQL.Client.Abstractions.Utilities.StringUtils.ToLowerFirst(str); + + public static string Capitalize(this string str) => GraphQL.Client.Abstractions.Utilities.StringUtils.Capitalize(str); + + public static string ToCamelCase(this string str) => GraphQL.Client.Abstractions.Utilities.StringUtils.ToCamelCase(str); + + public static string ToConstantCase(this string str) => GraphQL.Client.Abstractions.Utilities.StringUtils.ToConstantCase(str); + + public static string ToUpperCase(this string str) => GraphQL.Client.Abstractions.Utilities.StringUtils.ToUpperCase(str); + + public static string ToLowerCase(this string str) => GraphQL.Client.Abstractions.Utilities.StringUtils.ToLowerCase(str); + + + public static string ToPascalCase(this string str) => GraphQL.Client.Abstractions.Utilities.StringUtils.ToPascalCase(str); + + + public static string ToKebabCase(this string str) => GraphQL.Client.Abstractions.Utilities.StringUtils.ToKebabCase(str); + + + public static string ToSnakeCase(this string str) => GraphQL.Client.Abstractions.Utilities.StringUtils.ToSnakeCase(str); + } +} diff --git a/src/GraphQL.Client.Abstractions/Utilities/StringUtils.cs b/src/GraphQL.Client.Abstractions/Utilities/StringUtils.cs new file mode 100644 index 00000000..c643fd23 --- /dev/null +++ b/src/GraphQL.Client.Abstractions/Utilities/StringUtils.cs @@ -0,0 +1,193 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +namespace GraphQL.Client.Abstractions.Utilities +{ + /// + /// Copied from https://github.com/jquense/StringUtils + /// + public static class StringUtils + { + private static readonly Regex _reWords = new Regex(@"[A-Z\xc0-\xd6\xd8-\xde]?[a-z\xdf-\xf6\xf8-\xff]+(?:['’](?:d|ll|m|re|s|t|ve))?(?=[\xac\xb1\xd7\xf7\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xbf\u2000-\u206f \t\x0b\f\xa0\ufeff\n\r\u2028\u2029\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000]|[A-Z\xc0-\xd6\xd8-\xde]|$)|(?:[A-Z\xc0-\xd6\xd8-\xde]|[^\ud800-\udfff\xac\xb1\xd7\xf7\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xbf\u2000-\u206f \t\x0b\f\xa0\ufeff\n\r\u2028\u2029\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\d+\u2700-\u27bfa-z\xdf-\xf6\xf8-\xffA-Z\xc0-\xd6\xd8-\xde])+(?:['’](?:D|LL|M|RE|S|T|VE))?(?=[\xac\xb1\xd7\xf7\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xbf\u2000-\u206f \t\x0b\f\xa0\ufeff\n\r\u2028\u2029\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000]|[A-Z\xc0-\xd6\xd8-\xde](?:[a-z\xdf-\xf6\xf8-\xff]|[^\ud800-\udfff\xac\xb1\xd7\xf7\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xbf\u2000-\u206f \t\x0b\f\xa0\ufeff\n\r\u2028\u2029\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\d+\u2700-\u27bfa-z\xdf-\xf6\xf8-\xffA-Z\xc0-\xd6\xd8-\xde])|$)|[A-Z\xc0-\xd6\xd8-\xde]?(?:[a-z\xdf-\xf6\xf8-\xff]|[^\ud800-\udfff\xac\xb1\xd7\xf7\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xbf\u2000-\u206f \t\x0b\f\xa0\ufeff\n\r\u2028\u2029\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\d+\u2700-\u27bfa-z\xdf-\xf6\xf8-\xffA-Z\xc0-\xd6\xd8-\xde])+(?:['’](?:d|ll|m|re|s|t|ve))?|[A-Z\xc0-\xd6\xd8-\xde]+(?:['’](?:D|LL|M|RE|S|T|VE))?|\d+|(?:[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff])[\ufe0e\ufe0f]?(?:[\u0300-\u036f\ufe20-\ufe23\u20d0-\u20f0]|\ud83c[\udffb-\udfff])?(?:\u200d(?:[^\ud800-\udfff]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff])[\ufe0e\ufe0f]?(?:[\u0300-\u036f\ufe20-\ufe23\u20d0-\u20f0]|\ud83c[\udffb-\udfff])?)*"); + private static readonly Regex _reIndent = new Regex(@"^[ \t]*(?=\S)", RegexOptions.Multiline); + + /// + /// Removes the leading indent from a multi-line string + /// + /// String + /// + public static string StripIndent(string str) + { + int indent = _reIndent.Matches(str).Cast().Select(m => m.Value.Length).Min(); + return new Regex(@"^[ \t]{" + indent + "}", RegexOptions.Multiline).Replace(str, ""); + } + + /// + /// Split a cased string into a series of "words" excluding the seperator. + /// + /// + /// + public static IEnumerable ToWords(string str) + { + foreach (Match match in _reWords.Matches(str)) + { + yield return match.Value; + } + } + + /// + /// Uppercase the first character in a string, leaving the rest of the string as is + /// + /// + /// a string with the first character uppercased + public static string ToUpperFirst(string str) => ChangeCaseFirst(str, c => c.ToUpperInvariant()); + + /// + /// Lowercase the first character in a string, leaving the rest of the string as is + /// + /// + /// a string with the first character lowercased + public static string ToLowerFirst(string str) => ChangeCaseFirst(str, c => c.ToLowerInvariant()); + + /// + /// Capitalizes a string, lowercasing the entire string and uppercasing the first character + /// + /// + /// a capitalized string + public static string Capitalize(string str) => ToUpperFirst(str.ToLowerInvariant()); + + /// + /// Converts a string to camelCase. + /// + /// + /// StringUtils.ToCamelCase("FOOBAR") // "foobar" + /// StringUtils.ToCamelCase("FOO_BAR") // "fooBar" + /// StringUtils.ToCamelCase("FooBar") // "fooBar" + /// StringUtils.ToCamelCase("foo bar") // "fooBar" + /// + /// + /// + public static string ToCamelCase(string str) => + ChangeCase(str, (word, index) => + (index == 0 ? word.ToLowerInvariant() : Capitalize(word))); + + /// + /// Convert a string to CONSTANT_CASE + /// + /// + /// StringUtils.ToConstantCase("fOo BaR") // "FOO_BAR" + /// StringUtils.ToConstantCase("FooBar") // "FOO_BAR" + /// StringUtils.ToConstantCase("Foo Bar") // "FOO_BAR" + /// + /// + /// + public static string ToConstantCase(string str) => ChangeCase(str, "_", w => w.ToUpperInvariant()); + + /// + /// Convert a string to UPPERCASE + /// + /// + /// StringUtils.ToUpperCase("foobar") // "FOOBAR" + /// StringUtils.ToUpperCase("FOO_BAR") // "FOO BAR" + /// StringUtils.ToUpperCase("FooBar") // "FOO BAR" + /// StringUtils.ToUpperCase("Foo Bar") // "FOO BAR" + /// + /// + /// + public static string ToUpperCase(string str) => ChangeCase(str, " ", (word) => word.ToUpperInvariant()); + + /// + /// Convert a string to lowercase + /// + /// + /// StringUtils.ToLowerCase("FOOBAR") // "foobar" + /// StringUtils.ToLowerCase("FOO_BAR") // "foo bar" + /// StringUtils.ToLowerCase("FooBar") // "foo bar" + /// StringUtils.ToLowerCase("Foo Bar") // "foo bar" + /// + /// + /// + public static string ToLowerCase(string str) => ChangeCase(str, " ", word => word.ToLowerInvariant()); + + /// + /// convert a string to PascalCase + /// + /// + /// StringUtils.ToPascalCase("FOOBAR") // "FooBar" + /// StringUtils.ToPascalCase("FOO_BAR") // "FooBar" + /// StringUtils.ToPascalCase("fooBar") // "FooBar" + /// StringUtils.ToPascalCase("Foo Bar") // "FooBar" + /// + /// + /// + public static string ToPascalCase(string str) => ChangeCase(str, Capitalize); + + /// + /// convert a string to kebab-case + /// + /// + /// StringUtils.ToKebabCase("FOOBAR") // "foo-bar" + /// StringUtils.ToKebabCase("FOO_BAR") // "foo-bar" + /// StringUtils.ToKebabCase("fooBar") // "foo-bar" + /// StringUtils.ToKebabCase("Foo Bar") // "foo-bar" + /// + /// + /// + public static string ToKebabCase(string str) => ChangeCase(str, "-", word => word.ToLowerInvariant()); + + /// + /// convert a string to snake_case + /// + /// + /// StringUtils.ToSnakeCase("FOOBAR") // "foo_bar" + /// StringUtils.ToSnakeCase("FOO_BAR") // "foo_bar" + /// StringUtils.ToSnakeCase("fooBar") // "foo_bar" + /// StringUtils.ToSnakeCase("Foo Bar") // "foo_bar" + /// + /// + /// + public static string ToSnakeCase(string str) => ChangeCase(str, "_", word => word.ToLowerInvariant()); + + public static string ChangeCase(string str, Func composer) => ChangeCase(str, "", composer); + + public static string ChangeCase(string str, string sep, Func composer) => ChangeCase(str, sep, (w, i) => composer(w)); + + public static string ChangeCase(string str, Func composer) => ChangeCase(str, "", composer); + + /// + /// Convert a string to a new case + /// + /// + /// Convert a string to inverse camelCase: CAMELcASE + /// + /// StringUtils.ChangeCase("my string", "", (word, index) => { + /// word = word.ToUpperInvariant(); + /// if (index > 0) + /// word = StringUtils.toLowerFirst(word); + /// return word + /// }); + /// // "MYsTRING" + /// + /// + /// an input string + /// a seperator string used between "words" in the string + /// a function that converts individual words to a new case + /// + public static string ChangeCase(string str, string sep, Func composer) + { + string result = ""; + int index = 0; + + foreach (string word in ToWords(str)) + { + result += ((index == 0 ? "" : sep) + composer(word, index++)); + } + + return result; + } + + private static string ChangeCaseFirst(string str, Func change) => change(str.Substring(0, 1)) + str.Substring(1); + } +} diff --git a/src/GraphQL.Client.Serializer.Newtonsoft/ConstantCaseEnumConverter.cs b/src/GraphQL.Client.Serializer.Newtonsoft/ConstantCaseEnumConverter.cs index 5eb45f73..d618a490 100644 --- a/src/GraphQL.Client.Serializer.Newtonsoft/ConstantCaseEnumConverter.cs +++ b/src/GraphQL.Client.Serializer.Newtonsoft/ConstantCaseEnumConverter.cs @@ -1,9 +1,9 @@ using System; using System.Linq; using System.Reflection; +using GraphQL.Client.Abstractions.Utilities; using Newtonsoft.Json; using Newtonsoft.Json.Converters; -using Panic.StringUtils.Extensions; namespace GraphQL.Client.Serializer.Newtonsoft { diff --git a/src/GraphQL.Client.Serializer.Newtonsoft/GraphQL.Client.Serializer.Newtonsoft.csproj b/src/GraphQL.Client.Serializer.Newtonsoft/GraphQL.Client.Serializer.Newtonsoft.csproj index f603fc93..4bbcb784 100644 --- a/src/GraphQL.Client.Serializer.Newtonsoft/GraphQL.Client.Serializer.Newtonsoft.csproj +++ b/src/GraphQL.Client.Serializer.Newtonsoft/GraphQL.Client.Serializer.Newtonsoft.csproj @@ -9,7 +9,6 @@ - diff --git a/src/GraphQL.Client.Serializer.SystemTextJson/ConstantCaseJsonNamingPolicy.cs b/src/GraphQL.Client.Serializer.SystemTextJson/ConstantCaseJsonNamingPolicy.cs index b2ea49eb..138b0276 100644 --- a/src/GraphQL.Client.Serializer.SystemTextJson/ConstantCaseJsonNamingPolicy.cs +++ b/src/GraphQL.Client.Serializer.SystemTextJson/ConstantCaseJsonNamingPolicy.cs @@ -1,5 +1,5 @@ using System.Text.Json; -using Panic.StringUtils.Extensions; +using GraphQL.Client.Abstractions.Utilities; namespace GraphQL.Client.Serializer.SystemTextJson { diff --git a/src/GraphQL.Client.Serializer.SystemTextJson/GraphQL.Client.Serializer.SystemTextJson.csproj b/src/GraphQL.Client.Serializer.SystemTextJson/GraphQL.Client.Serializer.SystemTextJson.csproj index 93fe58af..9433b8dd 100644 --- a/src/GraphQL.Client.Serializer.SystemTextJson/GraphQL.Client.Serializer.SystemTextJson.csproj +++ b/src/GraphQL.Client.Serializer.SystemTextJson/GraphQL.Client.Serializer.SystemTextJson.csproj @@ -15,8 +15,4 @@ - - - - From 43407c5438c08adee07bf675a95b4b3f36f8bc7c Mon Sep 17 00:00:00 2001 From: Steve Bjorg Date: Tue, 6 Oct 2020 13:19:29 -0700 Subject: [PATCH 325/455] fix Blazor WASM check --- src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs index aba35e8f..d8476b72 100644 --- a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs +++ b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs @@ -408,7 +408,7 @@ public Task InitializeWebSocket() #else _clientWebSocket = new ClientWebSocket(); _clientWebSocket.Options.AddSubProtocol("graphql-ws"); - if(!System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Create("WEBASSEMBLY"))) + if(!System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Create("BROWSER"))) { // the following properties are not supported in Blazor WebAssembly and throw a PlatformNotSupportedException error when accessed _clientWebSocket.Options.ClientCertificates = ((HttpClientHandler)Options.HttpMessageHandler).ClientCertificates; From 0dcc99517e4b997efe07980efdec249b23c34542 Mon Sep 17 00:00:00 2001 From: Steve Bjorg Date: Tue, 6 Oct 2020 13:25:46 -0700 Subject: [PATCH 326/455] always use Debug.WriteLine() --- .../Websocket/GraphQLHttpWebSocket.cs | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs index aba35e8f..365c53ad 100644 --- a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs +++ b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs @@ -136,6 +136,7 @@ public IObservable> CreateSubscriptionStream( response.MessageBytes); + Debug.WriteLine($"payload => {System.Text.Encoding.UTF8.GetString(response.MessageBytes)}"); o.OnNext(typedResponse.Payload); // in case of a GraphQL error, terminate the sequence after the response has been posted @@ -194,7 +195,7 @@ public IObservable> CreateSubscriptionStream> CreateSubscriptionStream> CreateSubscriptionStream { - Debug.WriteLine($"unwrap exception thread id: {Thread.CurrentThread.ManagedThreadId}"); // if the result contains an exception, throw it on the observable if (t.Item2 != null) + { + Debug.WriteLine($"unwrap exception thread id: {Thread.CurrentThread.ManagedThreadId} => {t.Item2}"); return Observable.Throw>(t.Item2); - - return t.Item1 == null - ? Observable.Empty>() - : Observable.Return(t.Item1); + } + if (t.Item1 == null) + { + Debug.WriteLine($"empty item thread id: {Thread.CurrentThread.ManagedThreadId}"); + return Observable.Empty>(); + } + return Observable.Return(t.Item1); }) // transform to hot observable and auto-connect .Publish().RefCount(); @@ -319,7 +324,7 @@ public Task> SendRequest(GraphQLRequest re } catch (Exception e) { - Console.WriteLine(e); + Debug.WriteLine(e); throw; } From 151e733342f78bf1ba2a4cc3007eac2a04f615e5 Mon Sep 17 00:00:00 2001 From: Steve Bjorg Date: Tue, 6 Oct 2020 13:44:58 -0700 Subject: [PATCH 327/455] preserve all entries in GraphQLRequest --- src/GraphQL.Client/GraphQLHttpRequest.cs | 2 +- src/GraphQL.Primitives/GraphQLRequest.cs | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/GraphQL.Client/GraphQLHttpRequest.cs b/src/GraphQL.Client/GraphQLHttpRequest.cs index 00f90d4a..eb3aac14 100644 --- a/src/GraphQL.Client/GraphQLHttpRequest.cs +++ b/src/GraphQL.Client/GraphQLHttpRequest.cs @@ -16,7 +16,7 @@ public GraphQLHttpRequest(string query, object? variables = null, string? operat { } - public GraphQLHttpRequest(GraphQLRequest other): base(other.Query, other.Variables, other.OperationName) + public GraphQLHttpRequest(GraphQLRequest other): base(other) { } diff --git a/src/GraphQL.Primitives/GraphQLRequest.cs b/src/GraphQL.Primitives/GraphQLRequest.cs index 72033552..19c7fded 100644 --- a/src/GraphQL.Primitives/GraphQLRequest.cs +++ b/src/GraphQL.Primitives/GraphQLRequest.cs @@ -49,6 +49,14 @@ public GraphQLRequest(string query, object? variables = null, string? operationN OperationName = operationName; } + public GraphQLRequest(IEnumerable> values) + { + foreach(var kv in values) + { + Add(kv.Key, kv.Value); + } + } + /// /// Returns a value that indicates whether this instance is equal to a specified object /// @@ -86,7 +94,7 @@ public override int GetHashCode() { unchecked { - var hashCode = Query.GetHashCode(); + var hashCode = Query?.GetHashCode() ?? 0; hashCode = (hashCode * 397) ^ OperationName?.GetHashCode() ?? 0; hashCode = (hashCode * 397) ^ Variables?.GetHashCode() ?? 0; return hashCode; From 840da16ec92416bc34a495942d2f396695894408 Mon Sep 17 00:00:00 2001 From: Steve Bjorg Date: Tue, 6 Oct 2020 14:53:31 -0700 Subject: [PATCH 328/455] make copy constructor more specific --- src/GraphQL.Primitives/GraphQLRequest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/GraphQL.Primitives/GraphQLRequest.cs b/src/GraphQL.Primitives/GraphQLRequest.cs index 19c7fded..22074321 100644 --- a/src/GraphQL.Primitives/GraphQLRequest.cs +++ b/src/GraphQL.Primitives/GraphQLRequest.cs @@ -49,9 +49,9 @@ public GraphQLRequest(string query, object? variables = null, string? operationN OperationName = operationName; } - public GraphQLRequest(IEnumerable> values) + public GraphQLRequest(GraphQLRequest other) { - foreach(var kv in values) + foreach(var kv in other) { Add(kv.Key, kv.Value); } From da8299212a42bcc4000130067a15aa8a8d608931 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Wed, 7 Oct 2020 21:53:20 +0200 Subject: [PATCH 329/455] add ConnectionParams object --- .../GraphQLWebSocketRequest.cs | 4 ++-- src/GraphQL.Client/GraphQLHttpClientOptions.cs | 6 ++++++ src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs | 8 ++++---- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketRequest.cs b/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketRequest.cs index 623e4271..c71b4e28 100644 --- a/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketRequest.cs +++ b/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketRequest.cs @@ -34,9 +34,9 @@ public string Type /// /// The payload of the websocket request /// - public GraphQLRequest Payload + public object Payload { - get => ContainsKey(PAYLOAD_KEY) ? (GraphQLRequest)this[PAYLOAD_KEY] : null; + get => ContainsKey(PAYLOAD_KEY) ? this[PAYLOAD_KEY] : null; set => this[PAYLOAD_KEY] = value; } diff --git a/src/GraphQL.Client/GraphQLHttpClientOptions.cs b/src/GraphQL.Client/GraphQLHttpClientOptions.cs index 6e2c3a46..ff36808f 100644 --- a/src/GraphQL.Client/GraphQLHttpClientOptions.cs +++ b/src/GraphQL.Client/GraphQLHttpClientOptions.cs @@ -56,5 +56,11 @@ public class GraphQLHttpClientOptions /// Configure additional websocket options (i.e. headers). This will not be invoked on Windows 7 when targeting .NET Framework 4.x. /// public Action ConfigureWebsocketOptions { get; set; } = options => { }; + + /// + /// The `ConnectionParams` object sent with the GQL_CONNECTION_INIT message on establishing a websocket connection. + /// See https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md#gql_connection_init. + /// + public object? WebSocketConnectionParams { get; set; } = null; } } diff --git a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs index dfdf39c4..8bf8adee 100644 --- a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs +++ b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs @@ -105,7 +105,7 @@ public IObservable> CreateSubscriptionStream> CreateSubscriptionStream>(o => @@ -179,8 +179,8 @@ public IObservable> CreateSubscriptionStream Date: Wed, 7 Oct 2020 23:40:41 +0200 Subject: [PATCH 330/455] use TryGetValue --- .../GraphQLWebSocketRequest.cs | 6 +++--- src/GraphQL.Primitives/GraphQLRequest.cs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketRequest.cs b/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketRequest.cs index c71b4e28..d1ea99d5 100644 --- a/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketRequest.cs +++ b/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketRequest.cs @@ -18,7 +18,7 @@ public class GraphQLWebSocketRequest : Dictionary, IEquatable public string Id { - get => ContainsKey(ID_KEY) ? (string)this[ID_KEY] : null; + get => TryGetValue(ID_KEY, out object value) ? (string)value : null; set => this[ID_KEY] = value; } @@ -27,7 +27,7 @@ public string Id /// public string Type { - get => ContainsKey(TYPE_KEY) ? (string)this[TYPE_KEY] : null; + get => TryGetValue(TYPE_KEY, out object value) ? (string)value : null; set => this[TYPE_KEY] = value; } @@ -36,7 +36,7 @@ public string Type /// public object Payload { - get => ContainsKey(PAYLOAD_KEY) ? this[PAYLOAD_KEY] : null; + get => TryGetValue(PAYLOAD_KEY, out object value) ? value : null; set => this[PAYLOAD_KEY] = value; } diff --git a/src/GraphQL.Primitives/GraphQLRequest.cs b/src/GraphQL.Primitives/GraphQLRequest.cs index 72033552..7727272d 100644 --- a/src/GraphQL.Primitives/GraphQLRequest.cs +++ b/src/GraphQL.Primitives/GraphQLRequest.cs @@ -18,7 +18,7 @@ public class GraphQLRequest : Dictionary, IEquatable public string Query { - get => ContainsKey(QUERY_KEY) ? (string)this[QUERY_KEY] : null; + get => TryGetValue(QUERY_KEY, out object value) ? (string)value : null; set => this[QUERY_KEY] = value; } @@ -27,7 +27,7 @@ public string Query /// public string? OperationName { - get => ContainsKey(OPERATION_NAME_KEY) ? (string)this[OPERATION_NAME_KEY] : null; + get => TryGetValue(OPERATION_NAME_KEY, out object value) ? (string)value : null; set => this[OPERATION_NAME_KEY] = value; } @@ -36,7 +36,7 @@ public string? OperationName /// public object? Variables { - get => ContainsKey(VARIABLES_KEY) ? this[VARIABLES_KEY] : null; + get => TryGetValue(VARIABLES_KEY, out object value) ? value : null; set => this[VARIABLES_KEY] = value; } From 5816b208427e5e07d7b8982253bf8fa11e53895f Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Wed, 7 Oct 2020 23:51:27 +0200 Subject: [PATCH 331/455] Use Func instead of static object to configure --- .../GraphQLWebSocketRequest.cs | 4 ++-- src/GraphQL.Client/GraphQLHttpClientOptions.cs | 2 +- src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs | 7 ++++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketRequest.cs b/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketRequest.cs index d1ea99d5..f83fe5aa 100644 --- a/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketRequest.cs +++ b/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketRequest.cs @@ -14,7 +14,7 @@ public class GraphQLWebSocketRequest : Dictionary, IEquatable - /// The Identifier of the Response + /// The Identifier of the request /// public string Id { @@ -34,7 +34,7 @@ public string Type /// /// The payload of the websocket request /// - public object Payload + public object? Payload { get => TryGetValue(PAYLOAD_KEY, out object value) ? value : null; set => this[PAYLOAD_KEY] = value; diff --git a/src/GraphQL.Client/GraphQLHttpClientOptions.cs b/src/GraphQL.Client/GraphQLHttpClientOptions.cs index ff36808f..be249baa 100644 --- a/src/GraphQL.Client/GraphQLHttpClientOptions.cs +++ b/src/GraphQL.Client/GraphQLHttpClientOptions.cs @@ -61,6 +61,6 @@ public class GraphQLHttpClientOptions /// The `ConnectionParams` object sent with the GQL_CONNECTION_INIT message on establishing a websocket connection. /// See https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md#gql_connection_init. /// - public object? WebSocketConnectionParams { get; set; } = null; + public Func SetWebSocketConnectionParams { get; set; } = () => null; } } diff --git a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs index 8bf8adee..74647c5d 100644 --- a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs +++ b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs @@ -98,12 +98,13 @@ public IObservable> CreateSubscriptionStream>(async observer => { Debug.WriteLine($"Create observable thread id: {Thread.CurrentThread.ManagedThreadId}"); - await _client.Options.PreprocessRequest(request, _client); + var preprocessedRequest = await _client.Options.PreprocessRequest(request, _client); + var startRequest = new GraphQLWebSocketRequest { Id = Guid.NewGuid().ToString("N"), Type = GraphQLWebSocketMessageType.GQL_START, - Payload = request + Payload = preprocessedRequest }; var stopRequest = new GraphQLWebSocketRequest { @@ -114,7 +115,7 @@ public IObservable> CreateSubscriptionStream>(o => From 0ca0ce9f1885ac44db912d678aa5783987f5557a Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Wed, 7 Oct 2020 23:53:36 +0200 Subject: [PATCH 332/455] fix naming --- src/GraphQL.Client/GraphQLHttpClientOptions.cs | 4 ++-- src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/GraphQL.Client/GraphQLHttpClientOptions.cs b/src/GraphQL.Client/GraphQLHttpClientOptions.cs index be249baa..ab8949c6 100644 --- a/src/GraphQL.Client/GraphQLHttpClientOptions.cs +++ b/src/GraphQL.Client/GraphQLHttpClientOptions.cs @@ -58,9 +58,9 @@ public class GraphQLHttpClientOptions public Action ConfigureWebsocketOptions { get; set; } = options => { }; /// - /// The `ConnectionParams` object sent with the GQL_CONNECTION_INIT message on establishing a websocket connection. + /// Sets the `ConnectionParams` object sent with the GQL_CONNECTION_INIT message on establishing a GraphQL websocket connection. /// See https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md#gql_connection_init. /// - public Func SetWebSocketConnectionParams { get; set; } = () => null; + public Func SetWebSocketConnectionInitPayload { get; set; } = () => null; } } diff --git a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs index 74647c5d..f26e3e54 100644 --- a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs +++ b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs @@ -115,7 +115,7 @@ public IObservable> CreateSubscriptionStream>(o => From 937dc9ca78e29fe5e91a30a1e35c64e1ef724efa Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Thu, 8 Oct 2020 23:07:26 +0200 Subject: [PATCH 333/455] add options as context for init payload --- src/GraphQL.Client/GraphQLHttpClientOptions.cs | 2 +- src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/GraphQL.Client/GraphQLHttpClientOptions.cs b/src/GraphQL.Client/GraphQLHttpClientOptions.cs index ab8949c6..c0054813 100644 --- a/src/GraphQL.Client/GraphQLHttpClientOptions.cs +++ b/src/GraphQL.Client/GraphQLHttpClientOptions.cs @@ -61,6 +61,6 @@ public class GraphQLHttpClientOptions /// Sets the `ConnectionParams` object sent with the GQL_CONNECTION_INIT message on establishing a GraphQL websocket connection. /// See https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md#gql_connection_init. /// - public Func SetWebSocketConnectionInitPayload { get; set; } = () => null; + public Func ConfigureWebSocketConnectionInitPayload { get; set; } = options => null; } } diff --git a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs index f26e3e54..28af82d6 100644 --- a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs +++ b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs @@ -115,7 +115,7 @@ public IObservable> CreateSubscriptionStream>(o => From bb8bcfe27b0efcfa171d9123cd40f902eb69b301 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Thu, 8 Oct 2020 22:29:54 +0200 Subject: [PATCH 334/455] send connection_init and await connection_ack --- .../GraphQLWebSocketMessageType.cs | 3 +- .../Websocket/GraphQLHttpWebSocket.cs | 69 ++++++++++++------- .../GraphQLWebsocketConnectionException.cs | 25 +++++++ 3 files changed, 69 insertions(+), 28 deletions(-) create mode 100644 src/GraphQL.Client/Websocket/GraphQLWebsocketConnectionException.cs diff --git a/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketMessageType.cs b/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketMessageType.cs index daa83114..3b0e0499 100644 --- a/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketMessageType.cs +++ b/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketMessageType.cs @@ -38,8 +38,7 @@ public static class GraphQLWebSocketMessageType public const string GQL_CONNECTION_KEEP_ALIVE = "ka"; // Server -> Client /// - /// Client sends this message in order to stop a running GraphQL operation execution (for example: unsubscribe) - /// id: string : operation id + /// Client sends this message to terminate the connection. /// public const string GQL_CONNECTION_TERMINATE = "connection_terminate"; // Client -> Server diff --git a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs index 28af82d6..aaf25046 100644 --- a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs +++ b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs @@ -8,6 +8,7 @@ using System.Reactive.Linq; using System.Reactive.Subjects; using System.Reactive.Threading.Tasks; +using System.Text; using System.Threading; using System.Threading.Tasks; using GraphQL.Client.Abstractions.Websocket; @@ -111,12 +112,6 @@ public IObservable> CreateSubscriptionStream>(o => IncomingMessageStream @@ -187,20 +182,8 @@ public IObservable> CreateSubscriptionStream SendWebSocketRequestAsync(GraphQLWebSocketRequest reque } await InitializeWebSocket(); - var requestBytes = _client.JsonSerializer.SerializeToBytes(request); - await _clientWebSocket.SendAsync( - new ArraySegment(requestBytes), - WebSocketMessageType.Text, - true, - _internalCancellationToken); + await SendWebSocketMessageAsync(request, _internalCancellationToken); request.SendCompleted(); } catch (Exception e) @@ -369,6 +347,16 @@ await _clientWebSocket.SendAsync( return Unit.Default; } + private async Task SendWebSocketMessageAsync(GraphQLWebSocketRequest request, CancellationToken cancellationToken = default) + { + var requestBytes = _client.JsonSerializer.SerializeToBytes(request); + await _clientWebSocket.SendAsync( + new ArraySegment(requestBytes), + WebSocketMessageType.Text, + true, + cancellationToken); + } + #endregion public Task InitializeWebSocket() @@ -469,9 +457,38 @@ private async Task ConnectAsync(CancellationToken token) Debug.WriteLine($"new incoming message stream {_incomingMessages.GetHashCode()} created"); _incomingMessagesConnection = new CompositeDisposable(maintenanceSubscription, connection); + + var initRequest = new GraphQLWebSocketRequest + { + Type = GraphQLWebSocketMessageType.GQL_CONNECTION_INIT, + Payload = Options.ConfigureWebSocketConnectionInitPayload(Options) + }; + + // setup task to await connection_ack message + var ackTask = _incomingMessages + .Where(response => response != null ) + .TakeUntil(response => response.Type == GraphQLWebSocketMessageType.GQL_CONNECTION_ACK || + response.Type == GraphQLWebSocketMessageType.GQL_CONNECTION_ERROR) + .FirstAsync() + .ToTask(); + + // send connection init + Debug.WriteLine($"sending connection init message"); + await SendWebSocketMessageAsync(initRequest); + var response = await ackTask; + + if (response.Type == GraphQLWebSocketMessageType.GQL_CONNECTION_ACK) + Debug.WriteLine($"connection acknowledged: {Encoding.UTF8.GetString(response.MessageBytes)}"); + else + { + var errorPayload = Encoding.UTF8.GetString(response.MessageBytes); + Debug.WriteLine($"connection error received: {errorPayload}"); + throw new GraphQLWebsocketConnectionException(errorPayload); + } } catch (Exception e) { + Debug.WriteLine($"failed to establish websocket connection"); _stateSubject.OnNext(GraphQLWebsocketConnectionState.Disconnected); _exceptionSubject.OnNext(e); throw; diff --git a/src/GraphQL.Client/Websocket/GraphQLWebsocketConnectionException.cs b/src/GraphQL.Client/Websocket/GraphQLWebsocketConnectionException.cs new file mode 100644 index 00000000..c6b2e831 --- /dev/null +++ b/src/GraphQL.Client/Websocket/GraphQLWebsocketConnectionException.cs @@ -0,0 +1,25 @@ +using System; +using System.Runtime.Serialization; + +namespace GraphQL.Client.Http.Websocket +{ + [Serializable] + public class GraphQLWebsocketConnectionException: Exception + { + public GraphQLWebsocketConnectionException() + { + } + + protected GraphQLWebsocketConnectionException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + + public GraphQLWebsocketConnectionException(string message) : base(message) + { + } + + public GraphQLWebsocketConnectionException(string message, Exception innerException) : base(message, innerException) + { + } + } +} From 1a4fc38b9bc16512ef63d95591b942ee7c84d8df Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Thu, 8 Oct 2020 22:42:15 +0200 Subject: [PATCH 335/455] send connection_terminate message on close --- src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs index aaf25046..350aae6a 100644 --- a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs +++ b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs @@ -617,6 +617,9 @@ private async Task CloseAsync() return; } + Debug.WriteLine($"send \"connection_terminate\" message"); + await SendWebSocketMessageAsync(new GraphQLWebSocketRequest{Type = GraphQLWebSocketMessageType.GQL_CONNECTION_TERMINATE}); + Debug.WriteLine($"closing websocket {_clientWebSocket.GetHashCode()}"); await _clientWebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None); _stateSubject.OnNext(GraphQLWebsocketConnectionState.Disconnected); From 75b07442c9d9492d18aa5090fe5e73ea42bd25b7 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Mon, 12 Oct 2020 21:47:46 +0200 Subject: [PATCH 336/455] fix syntax for environment vars --- .github/workflows/branches.yml | 8 ++++---- .github/workflows/master.yml | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/branches.yml b/.github/workflows/branches.yml index 2db62d5a..f1e31304 100644 --- a/.github/workflows/branches.yml +++ b/.github/workflows/branches.yml @@ -23,12 +23,12 @@ jobs: - name: Fetch complete repository including tags run: git fetch --tags --force --prune && git describe - name: Generate version info from git history - run: dotnet gitversion /output json > gitversion.json && cat gitversion.json + run: dotnet gitversion /output json | jq -r 'to_entries|map("GitVersion_\(.key)=\(.value|tostring)")|.[]' >> gitversion.env && cat gitversion.env - name: Upload version info file uses: actions/upload-artifact@v1 with: name: gitversion - path: gitversion.json + path: gitversion.env build: name: Build @@ -43,7 +43,7 @@ jobs: name: gitversion path: ./ - name: Inject version info into environment - run: jq -r 'to_entries|map("::set-env name=GitVersion_\(.key)::\(.value|tostring)")|.[]' gitversion.json + run: cat ./gitversion.env >> $GITHUB_ENV - name: Build solution run: echo "Current version is \"$GitVersion_SemVer\"" && dotnet build -c Release - name: Create NuGet packages @@ -67,6 +67,6 @@ jobs: name: gitversion path: ./ - name: Inject version info into environment - run: jq -r 'to_entries|map("::set-env name=GitVersion_\(.key)::\(.value|tostring)")|.[]' gitversion.json + run: cat ./gitversion.env >> $GITHUB_ENV - name: Run tests run: echo "Current version is \"$GitVersion_SemVer\"" && dotnet test -c Release diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index e7e29460..407156d8 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -26,12 +26,12 @@ jobs: - name: Fetch complete repository including tags run: git fetch --tags --force --prune && git describe - name: Generate version info from git history - run: dotnet gitversion /output json > gitversion.json && cat gitversion.json + run: dotnet gitversion /output json | jq -r 'to_entries|map("GitVersion_\(.key)=\(.value|tostring)")|.[]' >> gitversion.env && cat gitversion.env - name: Upload version info file uses: actions/upload-artifact@v1 with: name: gitversion - path: gitversion.json + path: gitversion.env build: name: Build @@ -46,7 +46,7 @@ jobs: name: gitversion path: ./ - name: Inject version info into environment - run: jq -r 'to_entries|map("::set-env name=GitVersion_\(.key)::\(.value|tostring)")|.[]' gitversion.json + run: cat ./gitversion.env >> $GITHUB_ENV - name: Build solution run: echo "Current version is \"$GitVersion_SemVer\"" && dotnet build -c Release - name: Create NuGet packages @@ -70,7 +70,7 @@ jobs: name: gitversion path: ./ - name: Inject version info into environment - run: jq -r 'to_entries|map("::set-env name=GitVersion_\(.key)::\(.value|tostring)")|.[]' gitversion.json + run: cat ./gitversion.env >> $GITHUB_ENV - name: Run tests run: echo "Current version is \"$GitVersion_SemVer\"" && dotnet test -c Release From d00f169961647e3ab2fb249a7ade7b8add201f70 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Mon, 12 Oct 2020 22:00:05 +0200 Subject: [PATCH 337/455] add detailed package shields --- README.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 32d35da0..0866ef7a 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,19 @@ # GraphQL.Client -[![NuGet](https://img.shields.io/nuget/v/GraphQL.Client.svg)](https://www.nuget.org/packages/GraphQL.Client) -[![NuGet](https://img.shields.io/nuget/vpre/GraphQL.Client.svg)](https://www.nuget.org/packages/GraphQL.Client) A GraphQL Client for .NET Standard over HTTP. +Provides the following packages: + +| Package | Downloads | Nuget Latest | +|---------|-----------|--------------| +| GraphQL.Client | [![Nuget](https://img.shields.io/nuget/dt/GraphQL.Client)](https://www.nuget.org/packages/GraphQL.Client/) | [![Nuget](https://img.shields.io/nuget/vpre/GraphQL.Client)](https://www.nuget.org/packages/GraphQL.Client) | +| GraphQL.Client.Abstractions | [![Nuget](https://img.shields.io/nuget/dt/GraphQL.Client.Abstractions)](https://www.nuget.org/packages/GraphQL.Client.Abstractions) | [![Nuget](https://img.shields.io/nuget/vpre/GraphQL.Client.Abstractions)](https://www.nuget.org/packages/GraphQL.Client.Abstractions) | +| GraphQL.Client.Abstractions.Websocket | [![Nuget](https://img.shields.io/nuget/dt/GraphQL.Client.Abstractions.Websocket)](https://www.nuget.org/packages/GraphQL.Client.Abstractions.Websocket) | [![Nuget](https://img.shields.io/nuget/vpre/GraphQL.Client.Abstractions.Websocket)](https://www.nuget.org/packages/GraphQL.Client.Abstractions.Websocket) | +| GraphQL.Client.LocalExecution | [![Nuget](https://img.shields.io/nuget/dt/GraphQL.Client.LocalExecution)](https://www.nuget.org/packages/GraphQL.Client.LocalExecution) | [![Nuget](https://img.shields.io/nuget/vpre/GraphQL.Client.LocalExecution)](https://www.nuget.org/packages/GraphQL.Client.LocalExecution) | +| GraphQL.Client.Serializer.Newtonsoft | [![Nuget](https://img.shields.io/nuget/dt/GraphQL.Client.Serializer.Newtonsoft)](https://www.nuget.org/packages/GraphQL.Client.Serializer.Newtonsoft) | [![Nuget](https://img.shields.io/nuget/vpre/GraphQL.Client.Serializer.Newtonsoft)](https://www.nuget.org/packages/GraphQL.Client.Serializer.Newtonsoft) | +| GraphQL.Client.Serializer.SystemTextJson | [![Nuget](https://img.shields.io/nuget/dt/GraphQL.Client.Serializer.SystemTextJson)](https://www.nuget.org/packages/GraphQL.Client.Serializer.SystemTextJson) | [![Nuget](https://img.shields.io/nuget/vpre/GraphQL.Client.Serializer.SystemTextJson)](https://www.nuget.org/packages/GraphQL.Client.Serializer.SystemTextJson) | +| GraphQL.Primitives | [![Nuget](https://img.shields.io/nuget/dt/GraphQL.Primitives)](https://www.nuget.org/packages/GraphQL.Primitives/) | [![Nuget](https://img.shields.io/nuget/vpre/GraphQL.Primitives)](https://www.nuget.org/packages/GraphQL.Primitives) | + ## Specification: The Library will try to follow the following standards and documents: From 2dd397d20dd0de60fbddceba6f59dbefbeb98d37 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Thu, 15 Oct 2020 10:02:00 +0200 Subject: [PATCH 338/455] use Dictionary copy constructor on new GraphQLRequest constructor --- src/GraphQL.Primitives/GraphQLRequest.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/GraphQL.Primitives/GraphQLRequest.cs b/src/GraphQL.Primitives/GraphQLRequest.cs index 22074321..7e0c2dea 100644 --- a/src/GraphQL.Primitives/GraphQLRequest.cs +++ b/src/GraphQL.Primitives/GraphQLRequest.cs @@ -49,13 +49,7 @@ public GraphQLRequest(string query, object? variables = null, string? operationN OperationName = operationName; } - public GraphQLRequest(GraphQLRequest other) - { - foreach(var kv in other) - { - Add(kv.Key, kv.Value); - } - } + public GraphQLRequest(GraphQLRequest other): base(other) { } /// /// Returns a value that indicates whether this instance is equal to a specified object From 15f10451437dde34bb6b7f98f0cf5069d2d75f56 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Thu, 15 Oct 2020 10:03:02 +0200 Subject: [PATCH 339/455] fix GraphQLRequest.GetHastCode() --- src/GraphQL.Primitives/GraphQLRequest.cs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/GraphQL.Primitives/GraphQLRequest.cs b/src/GraphQL.Primitives/GraphQLRequest.cs index 7e0c2dea..d471077f 100644 --- a/src/GraphQL.Primitives/GraphQLRequest.cs +++ b/src/GraphQL.Primitives/GraphQLRequest.cs @@ -84,16 +84,7 @@ public virtual bool Equals(GraphQLRequest? other) /// /// /// - public override int GetHashCode() - { - unchecked - { - var hashCode = Query?.GetHashCode() ?? 0; - hashCode = (hashCode * 397) ^ OperationName?.GetHashCode() ?? 0; - hashCode = (hashCode * 397) ^ Variables?.GetHashCode() ?? 0; - return hashCode; - } - } + public override int GetHashCode() => (Query, OperationName, Variables).GetHashCode(); /// /// Tests whether two specified instances are equivalent From d786f80ea1ec87e2106d7a00f43fccc0c73854f8 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Thu, 15 Oct 2020 11:56:04 +0200 Subject: [PATCH 340/455] allow configuration of dedicated websocket endpoint --- src/GraphQL.Client/GraphQLHttpClient.cs | 21 +++++++++++++--- .../GraphQLHttpClientOptions.cs | 7 +++++- src/GraphQL.Client/UriExtensions.cs | 9 +++++-- .../WebsocketTests/Base.cs | 24 +++++++++++++++++++ 4 files changed, 55 insertions(+), 6 deletions(-) diff --git a/src/GraphQL.Client/GraphQLHttpClient.cs b/src/GraphQL.Client/GraphQLHttpClient.cs index 21485f01..269eebda 100644 --- a/src/GraphQL.Client/GraphQLHttpClient.cs +++ b/src/GraphQL.Client/GraphQLHttpClient.cs @@ -64,8 +64,8 @@ public GraphQLHttpClient(GraphQLHttpClientOptions options, IGraphQLWebsocketJson if (!HttpClient.DefaultRequestHeaders.UserAgent.Any()) HttpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(GetType().Assembly.GetName().Name, GetType().Assembly.GetName().Version.ToString())); - - _lazyHttpWebSocket = new Lazy(() => new GraphQLHttpWebSocket(Options.EndPoint.GetWebSocketUri(), this)); + + _lazyHttpWebSocket = new Lazy(CreateGraphQLHttpWebSocket); } #endregion @@ -75,7 +75,9 @@ public GraphQLHttpClient(GraphQLHttpClientOptions options, IGraphQLWebsocketJson /// public async Task> SendQueryAsync(GraphQLRequest request, CancellationToken cancellationToken = default) { - if (Options.UseWebSocketForQueriesAndMutations || Options.EndPoint.HasWebSocketScheme()) + if (Options.UseWebSocketForQueriesAndMutations || + !(Options.WebSocketEndPoint is null) && Options.EndPoint is null || + Options.EndPoint.HasWebSocketScheme()) return await _graphQlHttpWebSocket.SendRequest(request, cancellationToken); return await SendHttpRequestAsync(request, cancellationToken); @@ -152,6 +154,19 @@ private async Task> SendHttpRequestAsync /// The GraphQL EndPoint to be used /// - public Uri EndPoint { get; set; } + public Uri? EndPoint { get; set; } + + /// + /// The GraphQL EndPoint to be used for websocket connections + /// + public Uri? WebSocketEndPoint { get; set; } = null; /// /// The that is going to be used diff --git a/src/GraphQL.Client/UriExtensions.cs b/src/GraphQL.Client/UriExtensions.cs index ad40a5bf..296eb052 100644 --- a/src/GraphQL.Client/UriExtensions.cs +++ b/src/GraphQL.Client/UriExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace GraphQL.Client.Http { @@ -9,7 +9,9 @@ public static class UriExtensions /// /// /// - public static bool HasWebSocketScheme(this Uri uri) => uri.Scheme.Equals("wss", StringComparison.OrdinalIgnoreCase) || uri.Scheme.Equals("ws", StringComparison.OrdinalIgnoreCase); + public static bool HasWebSocketScheme(this Uri? uri) => + !(uri is null) && + (uri.Scheme.Equals("wss", StringComparison.OrdinalIgnoreCase) || uri.Scheme.Equals("ws", StringComparison.OrdinalIgnoreCase)); /// /// Infers the websocket uri from . @@ -18,6 +20,9 @@ public static class UriExtensions /// public static Uri GetWebSocketUri(this Uri uri) { + if (uri is null) + throw new ArgumentNullException(nameof(uri)); + if (uri.HasWebSocketScheme()) return uri; diff --git a/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs b/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs index 4f90c1a5..ba3ccb17 100644 --- a/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs +++ b/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs @@ -79,6 +79,30 @@ public async void CanUseWebSocketScheme() var response = await ChatClient.AddMessageAsync(message); response.Data.AddMessage.Content.Should().Be(message); } + + [Fact] + public async void CanUseDedicatedWebSocketEndpoint() + { + ChatClient.Options.WebSocketEndPoint = ChatClient.Options.EndPoint.GetWebSocketUri(); + ChatClient.Options.EndPoint = new Uri("http://bad-endpoint.test"); + ChatClient.Options.UseWebSocketForQueriesAndMutations = true; + await ChatClient.InitializeWebsocketConnection(); + const string message = "some random testing message"; + var response = await ChatClient.AddMessageAsync(message); + response.Data.AddMessage.Content.Should().Be(message); + } + + [Fact] + public async void CanUseDedicatedWebSocketEndpointWithoutHttpEndpoint() + { + ChatClient.Options.WebSocketEndPoint = ChatClient.Options.EndPoint.GetWebSocketUri(); + ChatClient.Options.EndPoint = null; + ChatClient.Options.UseWebSocketForQueriesAndMutations = false; + await ChatClient.InitializeWebsocketConnection(); + const string message = "some random testing message"; + var response = await ChatClient.AddMessageAsync(message); + response.Data.AddMessage.Content.Should().Be(message); + } [Fact] public async void WebsocketRequestCanBeCancelled() From 14f30c99c65d00f0be681c9e0f70fce22ee964a5 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Thu, 7 Jan 2021 21:04:37 +0100 Subject: [PATCH 341/455] catch PlatformNotSupportedException for unsupported Options properties --- .../Websocket/GraphQLHttpWebSocket.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs index 350aae6a..8e474693 100644 --- a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs +++ b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs @@ -402,12 +402,26 @@ public Task InitializeWebSocket() #else _clientWebSocket = new ClientWebSocket(); _clientWebSocket.Options.AddSubProtocol("graphql-ws"); - if(!System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Create("BROWSER"))) + + // the following properties are not supported in Blazor WebAssembly and throw a PlatformNotSupportedException error when accessed + try { - // the following properties are not supported in Blazor WebAssembly and throw a PlatformNotSupportedException error when accessed _clientWebSocket.Options.ClientCertificates = ((HttpClientHandler)Options.HttpMessageHandler).ClientCertificates; + } + catch (PlatformNotSupportedException) + { + Debug.WriteLine("property 'ClientWebSocketOptions.ClientCertificates' not supported by current platform"); + } + + try + { _clientWebSocket.Options.UseDefaultCredentials = ((HttpClientHandler)Options.HttpMessageHandler).UseDefaultCredentials; } + catch (PlatformNotSupportedException) + { + Debug.WriteLine("Property 'ClientWebSocketOptions.UseDefaultCredentials' not supported by current platform"); + } + Options.ConfigureWebsocketOptions(_clientWebSocket.Options); #endif return _initializeWebSocketTask = ConnectAsync(_internalCancellationToken); From 4d9c6b84784fa53160de6c06ac82715acf0622e3 Mon Sep 17 00:00:00 2001 From: David Welch Date: Sun, 31 Jan 2021 10:57:28 -0700 Subject: [PATCH 342/455] Catch NotImplementedException #324 Fixes #324 --- src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs index 8e474693..f63bf17c 100644 --- a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs +++ b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs @@ -408,6 +408,10 @@ public Task InitializeWebSocket() { _clientWebSocket.Options.ClientCertificates = ((HttpClientHandler)Options.HttpMessageHandler).ClientCertificates; } + catch (NotImplementedException) + { + Debug.WriteLine("property 'ClientWebSocketOptions.ClientCertificates' not implemented by current platform"); + } catch (PlatformNotSupportedException) { Debug.WriteLine("property 'ClientWebSocketOptions.ClientCertificates' not supported by current platform"); @@ -417,6 +421,10 @@ public Task InitializeWebSocket() { _clientWebSocket.Options.UseDefaultCredentials = ((HttpClientHandler)Options.HttpMessageHandler).UseDefaultCredentials; } + catch (NotImplementedException) + { + Debug.WriteLine("property 'ClientWebSocketOptions.UseDefaultCredentials' not implemented by current platform"); + } catch (PlatformNotSupportedException) { Debug.WriteLine("Property 'ClientWebSocketOptions.UseDefaultCredentials' not supported by current platform"); From 436912d4a9df1940cd32c6bdcdacd8d7d78918d3 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Fri, 2 Apr 2021 10:37:40 +0200 Subject: [PATCH 343/455] fix property name --- src/GraphQL.Client/GraphQLHttpClient.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/GraphQL.Client/GraphQLHttpClient.cs b/src/GraphQL.Client/GraphQLHttpClient.cs index 269eebda..f8d3640d 100644 --- a/src/GraphQL.Client/GraphQLHttpClient.cs +++ b/src/GraphQL.Client/GraphQLHttpClient.cs @@ -16,7 +16,7 @@ namespace GraphQL.Client.Http public class GraphQLHttpClient : IGraphQLClient { private readonly Lazy _lazyHttpWebSocket; - private GraphQLHttpWebSocket _graphQlHttpWebSocket => _lazyHttpWebSocket.Value; + private GraphQLHttpWebSocket GraphQlHttpWebSocket => _lazyHttpWebSocket.Value; private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); private readonly ConcurrentDictionary, object> _subscriptionStreams = new ConcurrentDictionary, object>(); @@ -39,12 +39,12 @@ public class GraphQLHttpClient : IGraphQLClient /// /// Publishes all exceptions which occur inside the websocket receive stream (i.e. for logging purposes) /// - public IObservable WebSocketReceiveErrors => _graphQlHttpWebSocket.ReceiveErrors; + public IObservable WebSocketReceiveErrors => GraphQlHttpWebSocket.ReceiveErrors; /// /// the websocket connection state /// - public IObservable WebsocketConnectionState => _graphQlHttpWebSocket.ConnectionState; + public IObservable WebsocketConnectionState => GraphQlHttpWebSocket.ConnectionState; #region Constructors @@ -78,7 +78,7 @@ public async Task> SendQueryAsync(GraphQLR if (Options.UseWebSocketForQueriesAndMutations || !(Options.WebSocketEndPoint is null) && Options.EndPoint is null || Options.EndPoint.HasWebSocketScheme()) - return await _graphQlHttpWebSocket.SendRequest(request, cancellationToken); + return await GraphQlHttpWebSocket.SendRequest(request, cancellationToken); return await SendHttpRequestAsync(request, cancellationToken); } @@ -99,7 +99,7 @@ public IObservable> CreateSubscriptionStream>)_subscriptionStreams[key]; - var observable = _graphQlHttpWebSocket.CreateSubscriptionStream(request); + var observable = GraphQlHttpWebSocket.CreateSubscriptionStream(request); _subscriptionStreams.TryAdd(key, observable); return observable; @@ -116,7 +116,7 @@ public IObservable> CreateSubscriptionStream>)_subscriptionStreams[key]; - var observable = _graphQlHttpWebSocket.CreateSubscriptionStream(request, exceptionHandler); + var observable = GraphQlHttpWebSocket.CreateSubscriptionStream(request, exceptionHandler); _subscriptionStreams.TryAdd(key, observable); return observable; } @@ -127,7 +127,7 @@ public IObservable> CreateSubscriptionStream /// - public Task InitializeWebsocketConnection() => _graphQlHttpWebSocket.InitializeWebSocket(); + public Task InitializeWebsocketConnection() => GraphQlHttpWebSocket.InitializeWebSocket(); #region Private Methods From a1c009fbb3f998674b8f390ffb2ae72156163104 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Fri, 2 Apr 2021 10:42:26 +0200 Subject: [PATCH 344/455] only dispose HttpClient with GraphQLHttpClient if it was created internally --- src/GraphQL.Client/GraphQLHttpClient.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/GraphQL.Client/GraphQLHttpClient.cs b/src/GraphQL.Client/GraphQLHttpClient.cs index f8d3640d..9d2aac47 100644 --- a/src/GraphQL.Client/GraphQLHttpClient.cs +++ b/src/GraphQL.Client/GraphQLHttpClient.cs @@ -21,6 +21,8 @@ public class GraphQLHttpClient : IGraphQLClient private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); private readonly ConcurrentDictionary, object> _subscriptionStreams = new ConcurrentDictionary, object>(); + private readonly bool _disposeHttpClient = false; + /// /// the json serializer /// @@ -54,7 +56,12 @@ public GraphQLHttpClient(Uri endPoint, IGraphQLWebsocketJsonSerializer serialize public GraphQLHttpClient(Action configure, IGraphQLWebsocketJsonSerializer serializer) : this(configure.New(), serializer) { } - public GraphQLHttpClient(GraphQLHttpClientOptions options, IGraphQLWebsocketJsonSerializer serializer) : this(options, serializer, new HttpClient(options.HttpMessageHandler)) { } + public GraphQLHttpClient(GraphQLHttpClientOptions options, IGraphQLWebsocketJsonSerializer serializer) : this( + options, serializer, new HttpClient(options.HttpMessageHandler)) + { + // set this flag to dispose the internally created HttpClient when GraphQLHttpClient gets disposed + _disposeHttpClient = true; + } public GraphQLHttpClient(GraphQLHttpClientOptions options, IGraphQLWebsocketJsonSerializer serializer, HttpClient httpClient) { @@ -195,7 +202,8 @@ protected virtual void Dispose(bool disposing) { Debug.WriteLine($"Disposing GraphQLHttpClient on endpoint {Options.EndPoint}"); _cancellationTokenSource.Cancel(); - HttpClient.Dispose(); + if(_disposeHttpClient) + HttpClient.Dispose(); if ( _lazyHttpWebSocket.IsValueCreated ) _lazyHttpWebSocket.Value.Dispose(); _cancellationTokenSource.Dispose(); From c9bd6665d3501c6b84331248305b4fcd466d6042 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Fri, 2 Apr 2021 11:24:09 +0200 Subject: [PATCH 345/455] update gitversion --- dotnet-tools.json | 2 +- src/src.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dotnet-tools.json b/dotnet-tools.json index 133edefa..3c532709 100644 --- a/dotnet-tools.json +++ b/dotnet-tools.json @@ -8,7 +8,7 @@ ] }, "gitversion.tool": { - "version": "5.2.4", + "version": "5.6.7", "commands": [ "dotnet-gitversion" ] diff --git a/src/src.props b/src/src.props index c4e46be1..37933c88 100644 --- a/src/src.props +++ b/src/src.props @@ -8,7 +8,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 35d6d118fdf1591708763ad5889d94b87f4820f6 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Sun, 6 Jun 2021 20:33:19 +0200 Subject: [PATCH 346/455] fix task to await connection_ack websocket message --- src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs index f63bf17c..ec4ac4b7 100644 --- a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs +++ b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs @@ -491,7 +491,7 @@ private async Task ConnectAsync(CancellationToken token) .Where(response => response != null ) .TakeUntil(response => response.Type == GraphQLWebSocketMessageType.GQL_CONNECTION_ACK || response.Type == GraphQLWebSocketMessageType.GQL_CONNECTION_ERROR) - .FirstAsync() + .LastAsync() .ToTask(); // send connection init From ce649f78b556f34663216f1adc155924e94a1b96 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Mon, 13 Sep 2021 14:50:51 +0200 Subject: [PATCH 347/455] update dependencies of integration tests --- .../Chat/AddMessageVariables.cs | 2 +- .../Chat/GraphQLClientChatExtensions.cs | 4 +-- .../Chat/Schema/ChatMutation.cs | 2 +- .../Chat/Schema/ChatSchema.cs | 13 +++++---- .../Chat/Schema/ChatSubscriptions.cs | 10 +++---- .../Chat/Schema/MessageType.cs | 2 +- tests/GraphQL.Client.Tests.Common/Common.cs | 2 -- .../GraphQL.Client.Tests.Common.csproj | 10 +++---- .../GraphQL.Integration.Tests.csproj | 4 +-- .../WebsocketTests/Base.cs | 5 ++++ .../IntegrationTestServer.csproj | 11 ++----- .../Properties/launchSettings.json | 4 +-- tests/IntegrationTestServer/Startup.cs | 29 +++++++++---------- 13 files changed, 47 insertions(+), 51 deletions(-) diff --git a/tests/GraphQL.Client.Tests.Common/Chat/AddMessageVariables.cs b/tests/GraphQL.Client.Tests.Common/Chat/AddMessageVariables.cs index 0a330007..887c55cd 100644 --- a/tests/GraphQL.Client.Tests.Common/Chat/AddMessageVariables.cs +++ b/tests/GraphQL.Client.Tests.Common/Chat/AddMessageVariables.cs @@ -12,7 +12,7 @@ public class AddMessageInput public string Content { get; set; } - public DateTime SentAt { get; set; } + public string SentAt { get; set; } } } } diff --git a/tests/GraphQL.Client.Tests.Common/Chat/GraphQLClientChatExtensions.cs b/tests/GraphQL.Client.Tests.Common/Chat/GraphQLClientChatExtensions.cs index f4417860..07648f06 100644 --- a/tests/GraphQL.Client.Tests.Common/Chat/GraphQLClientChatExtensions.cs +++ b/tests/GraphQL.Client.Tests.Common/Chat/GraphQLClientChatExtensions.cs @@ -10,7 +10,7 @@ public static class GraphQLClientChatExtensions @"mutation($input: MessageInputType){ addMessage(message: $input){ content - } + } }"; public static Task> AddMessageAsync(this IGraphQLClient client, string message) @@ -21,7 +21,7 @@ public static Task> AddMessageAsync(th { FromId = "2", Content = message, - SentAt = DateTime.Now + SentAt = DateTime.Now.ToString("s") } }; diff --git a/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatMutation.cs b/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatMutation.cs index 1d2cdd28..b005ebc2 100644 --- a/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatMutation.cs +++ b/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatMutation.cs @@ -36,7 +36,7 @@ public MessageInputType() { Field("fromId"); Field("content"); - Field("sentAt"); + Field("sentAt"); } } } diff --git a/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatSchema.cs b/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatSchema.cs index 49126f8a..fbdfedf1 100644 --- a/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatSchema.cs +++ b/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatSchema.cs @@ -1,13 +1,16 @@ +using System; +using Microsoft.Extensions.DependencyInjection; + namespace GraphQL.Client.Tests.Common.Chat.Schema { public class ChatSchema : Types.Schema { - public ChatSchema(IDependencyResolver resolver) - : base(resolver) + public ChatSchema(IServiceProvider services) + : base(services) { - Query = resolver.Resolve(); - Mutation = resolver.Resolve(); - Subscription = resolver.Resolve(); + Query = services.GetRequiredService(); + Mutation = services.GetRequiredService(); + Subscription = services.GetRequiredService(); } } } diff --git a/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatSubscriptions.cs b/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatSubscriptions.cs index f0d08efb..b15ba334 100644 --- a/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatSubscriptions.cs +++ b/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatSubscriptions.cs @@ -61,9 +61,9 @@ public ChatSubscriptions(IChat chat) }); } - private IObservable SubscribeById(ResolveEventStreamContext context) + private IObservable SubscribeById(IResolveEventStreamContext context) { - var messageContext = context.UserContext.As(); + var messageContext = (MessageHandlingContext) context.UserContext; var user = messageContext.Get("user"); var sub = "Anonymous"; @@ -76,16 +76,16 @@ private IObservable SubscribeById(ResolveEventStreamContext context) return messages.Where(message => message.From.Id == id); } - private Message ResolveMessage(ResolveFieldContext context) + private Message ResolveMessage(IResolveFieldContext context) { var message = context.Source as Message; return message; } - private IObservable Subscribe(ResolveEventStreamContext context) + private IObservable Subscribe(IResolveEventStreamContext context) { - var messageContext = context.UserContext.As(); + var messageContext = (MessageHandlingContext) context.UserContext; var user = messageContext.Get("user"); var sub = "Anonymous"; diff --git a/tests/GraphQL.Client.Tests.Common/Chat/Schema/MessageType.cs b/tests/GraphQL.Client.Tests.Common/Chat/Schema/MessageType.cs index 2636390c..e72a0067 100644 --- a/tests/GraphQL.Client.Tests.Common/Chat/Schema/MessageType.cs +++ b/tests/GraphQL.Client.Tests.Common/Chat/Schema/MessageType.cs @@ -12,7 +12,7 @@ public MessageType() Field(o => o.From, false, typeof(MessageFromType)).Resolve(ResolveFrom); } - private MessageFrom ResolveFrom(ResolveFieldContext context) + private MessageFrom ResolveFrom(IResolveFieldContext context) { var message = context.Source; return message.From; diff --git a/tests/GraphQL.Client.Tests.Common/Common.cs b/tests/GraphQL.Client.Tests.Common/Common.cs index 97dd30fe..af437bad 100644 --- a/tests/GraphQL.Client.Tests.Common/Common.cs +++ b/tests/GraphQL.Client.Tests.Common/Common.cs @@ -13,7 +13,6 @@ public static class Common public static StarWarsSchema GetStarWarsSchema() { var services = new ServiceCollection(); - services.AddTransient(provider => new FuncDependencyResolver(provider.GetService)); services.AddStarWarsSchema(); return services.BuildServiceProvider().GetRequiredService(); } @@ -21,7 +20,6 @@ public static StarWarsSchema GetStarWarsSchema() public static ChatSchema GetChatSchema() { var services = new ServiceCollection(); - services.AddTransient(provider => new FuncDependencyResolver(provider.GetService)); services.AddChatSchema(); return services.BuildServiceProvider().GetRequiredService(); } diff --git a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj index 6aeff4a3..f2fefe45 100644 --- a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj +++ b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj @@ -7,13 +7,11 @@ - - + + - - - - + + diff --git a/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj b/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj index 3cc7fd29..6aca8a5b 100644 --- a/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj +++ b/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj @@ -8,9 +8,7 @@ - - - + diff --git a/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs b/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs index ba3ccb17..4bedde89 100644 --- a/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs +++ b/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs @@ -67,6 +67,7 @@ public async void CanSendRequestViaWebsocket() await ChatClient.InitializeWebsocketConnection(); const string message = "some random testing message"; var response = await ChatClient.AddMessageAsync(message); + response.Errors.Should().BeNullOrEmpty(); response.Data.AddMessage.Content.Should().Be(message); } @@ -77,6 +78,7 @@ public async void CanUseWebSocketScheme() await ChatClient.InitializeWebsocketConnection(); const string message = "some random testing message"; var response = await ChatClient.AddMessageAsync(message); + response.Errors.Should().BeNullOrEmpty(); response.Data.AddMessage.Content.Should().Be(message); } @@ -89,6 +91,7 @@ public async void CanUseDedicatedWebSocketEndpoint() await ChatClient.InitializeWebsocketConnection(); const string message = "some random testing message"; var response = await ChatClient.AddMessageAsync(message); + response.Errors.Should().BeNullOrEmpty(); response.Data.AddMessage.Content.Should().Be(message); } @@ -174,10 +177,12 @@ public async void CanCreateObservableSubscription() Debug.WriteLine("subscribing..."); using var observer = observable.Observe(); await observer.Should().PushAsync(1); + observer.RecordedMessages.Last().Errors.Should().BeNullOrEmpty(); observer.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(InitialMessage.Content); const string message1 = "Hello World"; var response = await ChatClient.AddMessageAsync(message1); + response.Errors.Should().BeNullOrEmpty(); response.Data.AddMessage.Content.Should().Be(message1); await observer.Should().PushAsync(2); observer.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(message1); diff --git a/tests/IntegrationTestServer/IntegrationTestServer.csproj b/tests/IntegrationTestServer/IntegrationTestServer.csproj index da280f0f..2eaf45ba 100644 --- a/tests/IntegrationTestServer/IntegrationTestServer.csproj +++ b/tests/IntegrationTestServer/IntegrationTestServer.csproj @@ -10,15 +10,10 @@ - - - - - - + - - + + diff --git a/tests/IntegrationTestServer/Properties/launchSettings.json b/tests/IntegrationTestServer/Properties/launchSettings.json index cd6aedd6..987589b7 100644 --- a/tests/IntegrationTestServer/Properties/launchSettings.json +++ b/tests/IntegrationTestServer/Properties/launchSettings.json @@ -22,7 +22,7 @@ "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, - "applicationUrl": "http://localhost:5005/" + "applicationUrl": "http://localhost:5005" } } -} \ No newline at end of file +} diff --git a/tests/IntegrationTestServer/Startup.cs b/tests/IntegrationTestServer/Startup.cs index 49586dc0..5faae7f7 100644 --- a/tests/IntegrationTestServer/Startup.cs +++ b/tests/IntegrationTestServer/Startup.cs @@ -2,6 +2,7 @@ using GraphQL.Client.Tests.Common; using GraphQL.Client.Tests.Common.Chat.Schema; using GraphQL.Server; +using GraphQL.Server.Ui.Altair; using GraphQL.Server.Ui.GraphiQL; using GraphQL.Server.Ui.Playground; using GraphQL.StarWars; @@ -12,6 +13,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; namespace IntegrationTestServer { @@ -32,16 +34,21 @@ public Startup(IConfiguration configuration, IWebHostEnvironment environment) public void ConfigureServices(IServiceCollection services) { services.Configure(options => options.AllowSynchronousIO = true); - - services.AddTransient(provider => new FuncDependencyResolver(provider.GetService)); + // services.AddChatSchema(); services.AddStarWarsSchema(); - services.AddGraphQL(options => + services.AddSingleton(); + services.AddGraphQL((options, services) => { options.EnableMetrics = true; - options.ExposeExceptions = Environment.IsDevelopment(); + var logger = services.GetRequiredService>(); + options.UnhandledExceptionDelegate = ctx => logger.LogError("{Error} occurred", ctx.OriginalException.Message); }) - .AddWebSockets(); + .AddErrorInfoProvider(opt => opt.ExposeExceptionStackTrace = Environment.IsDevelopment()) + .AddSystemTextJson() + .AddWebSockets() + .AddGraphTypes(typeof(ChatSchema)) + .AddGraphTypes(typeof(StarWarsSchema)); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -57,16 +64,8 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) ConfigureGraphQLSchema(app, Common.CHAT_ENDPOINT); ConfigureGraphQLSchema(app, Common.STAR_WARS_ENDPOINT); - app.UseGraphiQLServer(new GraphiQLOptions - { - GraphiQLPath = "/ui/graphiql", - GraphQLEndPoint = Common.STAR_WARS_ENDPOINT - }); - app.UseGraphQLPlayground(new GraphQLPlaygroundOptions - { - Path = "/ui/playground", - GraphQLEndPoint = Common.CHAT_ENDPOINT - }); + app.UseGraphQLGraphiQL(new GraphiQLOptions { GraphQLEndPoint = Common.STAR_WARS_ENDPOINT }); + app.UseGraphQLAltair(new AltairOptions { GraphQLEndPoint = Common.CHAT_ENDPOINT }); } private void ConfigureGraphQLSchema(IApplicationBuilder app, string endpoint) where TSchema : Schema From fe110b6201d6165bb1bcad6065b738921137c98f Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Tue, 14 Sep 2021 10:36:52 +0200 Subject: [PATCH 348/455] add StarWars schema classes, fix local execution client --- .../GraphQL.Client.LocalExecution.csproj | 6 +- .../GraphQLLocalExecutionClient.cs | 10 ++- .../BaseSerializerTest.cs | 1 + tests/GraphQL.Client.Tests.Common/Common.cs | 4 +- .../GraphQL.Client.Tests.Common.csproj | 1 - .../ResolveFieldContextExtensions.cs | 65 ++++++++++++++ .../StarWars/StarWarsData.cs | 90 +++++++++++++++++++ .../StarWars/StarWarsMutation.cs | 35 ++++++++ .../StarWars/StarWarsQuery.cs | 33 +++++++ .../StarWars/StarWarsSchema.cs | 18 ++++ .../StarWars/{ => TestData}/StarWarsHumans.cs | 5 +- .../StarWars/Types/CharacterInterface.cs | 20 +++++ .../StarWars/Types/DroidType.cs | 30 +++++++ .../StarWars/Types/EpisodeEnum.cs | 23 +++++ .../StarWars/Types/HumanInputType.cs | 14 +++ .../StarWars/Types/HumanType.cs | 30 +++++++ .../StarWars/Types/StarWarsCharacter.cs | 23 +++++ .../QueryAndMutationTests/Base.cs | 1 + .../IntegrationTestServer.csproj | 1 - tests/IntegrationTestServer/Startup.cs | 3 +- 20 files changed, 400 insertions(+), 13 deletions(-) create mode 100644 tests/GraphQL.Client.Tests.Common/StarWars/Extensions/ResolveFieldContextExtensions.cs create mode 100644 tests/GraphQL.Client.Tests.Common/StarWars/StarWarsData.cs create mode 100644 tests/GraphQL.Client.Tests.Common/StarWars/StarWarsMutation.cs create mode 100644 tests/GraphQL.Client.Tests.Common/StarWars/StarWarsQuery.cs create mode 100644 tests/GraphQL.Client.Tests.Common/StarWars/StarWarsSchema.cs rename tests/GraphQL.Client.Tests.Common/StarWars/{ => TestData}/StarWarsHumans.cs (76%) create mode 100644 tests/GraphQL.Client.Tests.Common/StarWars/Types/CharacterInterface.cs create mode 100644 tests/GraphQL.Client.Tests.Common/StarWars/Types/DroidType.cs create mode 100644 tests/GraphQL.Client.Tests.Common/StarWars/Types/EpisodeEnum.cs create mode 100644 tests/GraphQL.Client.Tests.Common/StarWars/Types/HumanInputType.cs create mode 100644 tests/GraphQL.Client.Tests.Common/StarWars/Types/HumanType.cs create mode 100644 tests/GraphQL.Client.Tests.Common/StarWars/Types/StarWarsCharacter.cs diff --git a/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj b/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj index e8e162cb..d69fc21b 100644 --- a/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj +++ b/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj @@ -8,9 +8,9 @@ - - - + + + diff --git a/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs b/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs index 65098ae8..f2c077f6 100644 --- a/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs +++ b/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using GraphQL.Client.Abstractions; using GraphQL.Client.Serializer.Newtonsoft; +using GraphQL.NewtonsoftJson; using GraphQL.Subscription; using GraphQL.Types; using Newtonsoft.Json; @@ -41,6 +42,7 @@ public class GraphQLLocalExecutionClient : IGraphQLClient where TSchema public IGraphQLJsonSerializer Serializer { get; } private readonly DocumentExecuter _documentExecuter; + private readonly DocumentWriter _documentWriter; public GraphQLLocalExecutionClient(TSchema schema, IGraphQLJsonSerializer serializer) { @@ -50,6 +52,7 @@ public GraphQLLocalExecutionClient(TSchema schema, IGraphQLJsonSerializer serial if (!Schema.Initialized) Schema.Initialize(); _documentExecuter = new DocumentExecuter(); + _documentWriter = new DocumentWriter(); } public void Dispose() { } @@ -109,12 +112,13 @@ private async Task ExecuteAsync(GraphQLRequest request, Cancell return result; } - private Task> ExecutionResultToGraphQLResponse(ExecutionResult executionResult, CancellationToken cancellationToken = default) + private async Task> ExecutionResultToGraphQLResponse(ExecutionResult executionResult, CancellationToken cancellationToken = default) { + string json = await _documentWriter.WriteToStringAsync(executionResult); // serialize result into utf8 byte stream - var resultStream = new MemoryStream(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(executionResult, _variablesSerializerSettings))); + var resultStream = new MemoryStream(Encoding.UTF8.GetBytes(json)); // deserialize using the provided serializer - return Serializer.DeserializeFromUtf8StreamAsync(resultStream, cancellationToken); + return await Serializer.DeserializeFromUtf8StreamAsync(resultStream, cancellationToken); } #endregion diff --git a/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs b/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs index 53cba0c1..863974ae 100644 --- a/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs +++ b/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs @@ -16,6 +16,7 @@ using GraphQL.Client.Tests.Common.Chat.Schema; using GraphQL.Client.Tests.Common.Helpers; using GraphQL.Client.Tests.Common.StarWars; +using GraphQL.Client.Tests.Common.StarWars.TestData; using Xunit; namespace GraphQL.Client.Serializer.Tests diff --git a/tests/GraphQL.Client.Tests.Common/Common.cs b/tests/GraphQL.Client.Tests.Common/Common.cs index af437bad..269ea257 100644 --- a/tests/GraphQL.Client.Tests.Common/Common.cs +++ b/tests/GraphQL.Client.Tests.Common/Common.cs @@ -1,6 +1,6 @@ using GraphQL.Client.Tests.Common.Chat.Schema; -using GraphQL.StarWars; -using GraphQL.StarWars.Types; +using GraphQL.Client.Tests.Common.StarWars; +using GraphQL.Client.Tests.Common.StarWars.Types; using Microsoft.Extensions.DependencyInjection; namespace GraphQL.Client.Tests.Common diff --git a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj index f2fefe45..24cbf419 100644 --- a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj +++ b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj @@ -9,7 +9,6 @@ - diff --git a/tests/GraphQL.Client.Tests.Common/StarWars/Extensions/ResolveFieldContextExtensions.cs b/tests/GraphQL.Client.Tests.Common/StarWars/Extensions/ResolveFieldContextExtensions.cs new file mode 100644 index 00000000..44ba5895 --- /dev/null +++ b/tests/GraphQL.Client.Tests.Common/StarWars/Extensions/ResolveFieldContextExtensions.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using GraphQL.Builders; +using GraphQL.Client.Tests.Common.StarWars.Types; +using GraphQL.Types.Relay.DataObjects; + +namespace GraphQL.Client.Tests.Common.StarWars.Extensions +{ + public static class ResolveFieldContextExtensions + { + public static Connection GetPagedResults(this IResolveConnectionContext context, StarWarsData data, List ids) where U : StarWarsCharacter + { + List idList; + List list; + string cursor; + string endCursor; + var pageSize = context.PageSize ?? 20; + + if (context.IsUnidirectional || context.After != null || context.Before == null) + { + if (context.After != null) + { + idList = ids + .SkipWhile(x => !Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(x)).Equals(context.After)) + .Take(context.First ?? pageSize).ToList(); + } + else + { + idList = ids + .Take(context.First ?? pageSize).ToList(); + } + } + else + { + if (context.Before != null) + { + idList = ids.Reverse() + .SkipWhile(x => !Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(x)).Equals(context.Before)) + .Take(context.Last ?? pageSize).ToList(); + } + else + { + idList = ids.Reverse() + .Take(context.Last ?? pageSize).ToList(); + } + } + + list = data.GetCharactersAsync(idList).Result as List ?? throw new InvalidOperationException($"GetCharactersAsync method should return list of '{typeof(U).Name}' items."); + cursor = list.Count > 0 ? list.Last().Cursor : null; + endCursor = ids.Count > 0 ? Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(ids.Last())) : null; + + return new Connection + { + Edges = list.Select(x => new Edge { Cursor = x.Cursor, Node = x }).ToList(), + TotalCount = list.Count, + PageInfo = new PageInfo + { + EndCursor = endCursor, + HasNextPage = endCursor == null ? false : cursor != endCursor, + } + }; + } + } +} diff --git a/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsData.cs b/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsData.cs new file mode 100644 index 00000000..f561ed87 --- /dev/null +++ b/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsData.cs @@ -0,0 +1,90 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using GraphQL.Client.Tests.Common.StarWars.Types; + +namespace GraphQL.Client.Tests.Common.StarWars +{ + public class StarWarsData + { + private readonly List _characters = new List(); + + public StarWarsData() + { + _characters.Add(new Human + { + Id = "1", + Name = "Luke", + Friends = new List { "3", "4" }, + AppearsIn = new[] { 4, 5, 6 }, + HomePlanet = "Tatooine", + Cursor = "MQ==" + }); + _characters.Add(new Human + { + Id = "2", + Name = "Vader", + AppearsIn = new[] { 4, 5, 6 }, + HomePlanet = "Tatooine", + Cursor = "Mg==" + }); + + _characters.Add(new Droid + { + Id = "3", + Name = "R2-D2", + Friends = new List { "1", "4" }, + AppearsIn = new[] { 4, 5, 6 }, + PrimaryFunction = "Astromech", + Cursor = "Mw==" + }); + _characters.Add(new Droid + { + Id = "4", + Name = "C-3PO", + AppearsIn = new[] { 4, 5, 6 }, + PrimaryFunction = "Protocol", + Cursor = "NA==" + }); + } + + public IEnumerable GetFriends(StarWarsCharacter character) + { + if (character == null) + { + return null; + } + + var friends = new List(); + var lookup = character.Friends; + if (lookup != null) + { + foreach (var c in _characters.Where(h => lookup.Contains(h.Id))) + friends.Add(c); + } + return friends; + } + + public StarWarsCharacter AddCharacter(StarWarsCharacter character) + { + character.Id = _characters.Count.ToString(); + _characters.Add(character); + return character; + } + + public Task GetHumanByIdAsync(string id) + { + return Task.FromResult(_characters.FirstOrDefault(h => h.Id == id && h is Human) as Human); + } + + public Task GetDroidByIdAsync(string id) + { + return Task.FromResult(_characters.FirstOrDefault(h => h.Id == id && h is Droid) as Droid); + } + + public Task> GetCharactersAsync(List guids) + { + return Task.FromResult(_characters.Where(c => guids.Contains(c.Id)).ToList()); + } + } +} diff --git a/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsMutation.cs b/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsMutation.cs new file mode 100644 index 00000000..cd577688 --- /dev/null +++ b/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsMutation.cs @@ -0,0 +1,35 @@ +using GraphQL.Client.Tests.Common.StarWars.Types; +using GraphQL.Types; + +namespace GraphQL.Client.Tests.Common.StarWars +{ + /// + /// This is an example JSON request for a mutation + /// { + /// "query": "mutation ($human:HumanInput!){ createHuman(human: $human) { id name } }", + /// "variables": { + /// "human": { + /// "name": "Boba Fett" + /// } + /// } + /// } + /// + public class StarWarsMutation : ObjectGraphType + { + public StarWarsMutation(StarWarsData data) + { + Name = "Mutation"; + + Field( + "createHuman", + arguments: new QueryArguments( + new QueryArgument> { Name = "human" } + ), + resolve: context => + { + var human = context.GetArgument("human"); + return data.AddCharacter(human); + }); + } + } +} diff --git a/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsQuery.cs b/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsQuery.cs new file mode 100644 index 00000000..c416e8c5 --- /dev/null +++ b/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsQuery.cs @@ -0,0 +1,33 @@ +using System; +using GraphQL.Client.Tests.Common.StarWars.Types; +using GraphQL.Types; + +namespace GraphQL.Client.Tests.Common.StarWars +{ + public class StarWarsQuery : ObjectGraphType + { + public StarWarsQuery(StarWarsData data) + { + Name = "Query"; + + Field("hero", resolve: context => data.GetDroidByIdAsync("3")); + Field( + "human", + arguments: new QueryArguments( + new QueryArgument> { Name = "id", Description = "id of the human" } + ), + resolve: context => data.GetHumanByIdAsync(context.GetArgument("id")) + ); + + Func func = (context, id) => data.GetDroidByIdAsync(id); + + FieldDelegate( + "droid", + arguments: new QueryArguments( + new QueryArgument> { Name = "id", Description = "id of the droid" } + ), + resolve: func + ); + } + } +} diff --git a/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsSchema.cs b/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsSchema.cs new file mode 100644 index 00000000..eff7cff4 --- /dev/null +++ b/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsSchema.cs @@ -0,0 +1,18 @@ +using System; +using GraphQL.Types; +using Microsoft.Extensions.DependencyInjection; + +namespace GraphQL.Client.Tests.Common.StarWars +{ + public class StarWarsSchema : Schema + { + public StarWarsSchema(IServiceProvider serviceProvider) + : base(serviceProvider) + { + Query = serviceProvider.GetRequiredService(); + Mutation = serviceProvider.GetRequiredService(); + + Description = "Example StarWars universe schema"; + } + } +} diff --git a/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsHumans.cs b/tests/GraphQL.Client.Tests.Common/StarWars/TestData/StarWarsHumans.cs similarity index 76% rename from tests/GraphQL.Client.Tests.Common/StarWars/StarWarsHumans.cs rename to tests/GraphQL.Client.Tests.Common/StarWars/TestData/StarWarsHumans.cs index 88c64759..1e93ccb6 100644 --- a/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsHumans.cs +++ b/tests/GraphQL.Client.Tests.Common/StarWars/TestData/StarWarsHumans.cs @@ -1,8 +1,11 @@ using System.Collections; using System.Collections.Generic; -namespace GraphQL.Client.Tests.Common.StarWars +namespace GraphQL.Client.Tests.Common.StarWars.TestData { + /// + /// Test data object + /// public class StarWarsHumans : IEnumerable { public IEnumerator GetEnumerator() diff --git a/tests/GraphQL.Client.Tests.Common/StarWars/Types/CharacterInterface.cs b/tests/GraphQL.Client.Tests.Common/StarWars/Types/CharacterInterface.cs new file mode 100644 index 00000000..e45db3d4 --- /dev/null +++ b/tests/GraphQL.Client.Tests.Common/StarWars/Types/CharacterInterface.cs @@ -0,0 +1,20 @@ +using GraphQL.Types; +using GraphQL.Types.Relay; + +namespace GraphQL.Client.Tests.Common.StarWars.Types +{ + public class CharacterInterface : InterfaceGraphType + { + public CharacterInterface() + { + Name = "Character"; + + Field>("id", "The id of the character.", resolve: context => context.Source.Id); + Field("name", "The name of the character.", resolve: context => context.Source.Name); + + Field>("friends"); + Field>>("friendsConnection"); + Field>("appearsIn", "Which movie they appear in."); + } + } +} diff --git a/tests/GraphQL.Client.Tests.Common/StarWars/Types/DroidType.cs b/tests/GraphQL.Client.Tests.Common/StarWars/Types/DroidType.cs new file mode 100644 index 00000000..8d8fc867 --- /dev/null +++ b/tests/GraphQL.Client.Tests.Common/StarWars/Types/DroidType.cs @@ -0,0 +1,30 @@ +using GraphQL.Client.Tests.Common.StarWars.Extensions; +using GraphQL.Types; + +namespace GraphQL.Client.Tests.Common.StarWars.Types +{ + public class DroidType : ObjectGraphType + { + public DroidType(StarWarsData data) + { + Name = "Droid"; + Description = "A mechanical creature in the Star Wars universe."; + + Field>("id", "The id of the droid.", resolve: context => context.Source.Id); + Field("name", "The name of the droid.", resolve: context => context.Source.Name); + + Field>("friends", resolve: context => data.GetFriends(context.Source)); + + Connection() + .Name("friendsConnection") + .Description("A list of a character's friends.") + .Bidirectional() + .Resolve(context => context.GetPagedResults(data, context.Source.Friends)); + + Field>("appearsIn", "Which movie they appear in."); + Field("primaryFunction", "The primary function of the droid.", resolve: context => context.Source.PrimaryFunction); + + Interface(); + } + } +} diff --git a/tests/GraphQL.Client.Tests.Common/StarWars/Types/EpisodeEnum.cs b/tests/GraphQL.Client.Tests.Common/StarWars/Types/EpisodeEnum.cs new file mode 100644 index 00000000..6823376e --- /dev/null +++ b/tests/GraphQL.Client.Tests.Common/StarWars/Types/EpisodeEnum.cs @@ -0,0 +1,23 @@ +using GraphQL.Types; + +namespace GraphQL.Client.Tests.Common.StarWars.Types +{ + public class EpisodeEnum : EnumerationGraphType + { + public EpisodeEnum() + { + Name = "Episode"; + Description = "One of the films in the Star Wars Trilogy."; + AddValue("NEWHOPE", "Released in 1977.", 4); + AddValue("EMPIRE", "Released in 1980.", 5); + AddValue("JEDI", "Released in 1983.", 6); + } + } + + public enum Episodes + { + NEWHOPE = 4, + EMPIRE = 5, + JEDI = 6 + } +} diff --git a/tests/GraphQL.Client.Tests.Common/StarWars/Types/HumanInputType.cs b/tests/GraphQL.Client.Tests.Common/StarWars/Types/HumanInputType.cs new file mode 100644 index 00000000..c44c381c --- /dev/null +++ b/tests/GraphQL.Client.Tests.Common/StarWars/Types/HumanInputType.cs @@ -0,0 +1,14 @@ +using GraphQL.Types; + +namespace GraphQL.Client.Tests.Common.StarWars.Types +{ + public class HumanInputType : InputObjectGraphType + { + public HumanInputType() + { + Name = "HumanInput"; + Field>("name"); + Field("homePlanet"); + } + } +} diff --git a/tests/GraphQL.Client.Tests.Common/StarWars/Types/HumanType.cs b/tests/GraphQL.Client.Tests.Common/StarWars/Types/HumanType.cs new file mode 100644 index 00000000..93da8165 --- /dev/null +++ b/tests/GraphQL.Client.Tests.Common/StarWars/Types/HumanType.cs @@ -0,0 +1,30 @@ +using GraphQL.Client.Tests.Common.StarWars.Extensions; +using GraphQL.Types; + +namespace GraphQL.Client.Tests.Common.StarWars.Types +{ + public class HumanType : ObjectGraphType + { + public HumanType(StarWarsData data) + { + Name = "Human"; + + Field>("id", "The id of the human.", resolve: context => context.Source.Id); + Field("name", "The name of the human.", resolve: context => context.Source.Name); + + Field>("friends", resolve: context => data.GetFriends(context.Source)); + + Connection() + .Name("friendsConnection") + .Description("A list of a character's friends.") + .Bidirectional() + .Resolve(context => context.GetPagedResults(data, context.Source.Friends)); + + Field>("appearsIn", "Which movie they appear in."); + + Field("homePlanet", "The home planet of the human.", resolve: context => context.Source.HomePlanet); + + Interface(); + } + } +} diff --git a/tests/GraphQL.Client.Tests.Common/StarWars/Types/StarWarsCharacter.cs b/tests/GraphQL.Client.Tests.Common/StarWars/Types/StarWarsCharacter.cs new file mode 100644 index 00000000..b39d4cdc --- /dev/null +++ b/tests/GraphQL.Client.Tests.Common/StarWars/Types/StarWarsCharacter.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; + +namespace GraphQL.Client.Tests.Common.StarWars.Types +{ + public abstract class StarWarsCharacter + { + public string Id { get; set; } + public string Name { get; set; } + public List Friends { get; set; } + public int[] AppearsIn { get; set; } + public string Cursor { get; set; } + } + + public class Human : StarWarsCharacter + { + public string HomePlanet { get; set; } + } + + public class Droid : StarWarsCharacter + { + public string PrimaryFunction { get; set; } + } +} diff --git a/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs b/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs index 5855e626..b1f56e62 100644 --- a/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs +++ b/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs @@ -9,6 +9,7 @@ using GraphQL.Client.Tests.Common.Chat.Schema; using GraphQL.Client.Tests.Common.Helpers; using GraphQL.Client.Tests.Common.StarWars; +using GraphQL.Client.Tests.Common.StarWars.TestData; using GraphQL.Integration.Tests.Helpers; using Microsoft.Extensions.DependencyInjection; using Xunit; diff --git a/tests/IntegrationTestServer/IntegrationTestServer.csproj b/tests/IntegrationTestServer/IntegrationTestServer.csproj index 2eaf45ba..fab13b6e 100644 --- a/tests/IntegrationTestServer/IntegrationTestServer.csproj +++ b/tests/IntegrationTestServer/IntegrationTestServer.csproj @@ -11,7 +11,6 @@ - diff --git a/tests/IntegrationTestServer/Startup.cs b/tests/IntegrationTestServer/Startup.cs index 5faae7f7..f41856d7 100644 --- a/tests/IntegrationTestServer/Startup.cs +++ b/tests/IntegrationTestServer/Startup.cs @@ -1,11 +1,10 @@ using GraphQL; using GraphQL.Client.Tests.Common; using GraphQL.Client.Tests.Common.Chat.Schema; +using GraphQL.Client.Tests.Common.StarWars; using GraphQL.Server; using GraphQL.Server.Ui.Altair; using GraphQL.Server.Ui.GraphiQL; -using GraphQL.Server.Ui.Playground; -using GraphQL.StarWars; using GraphQL.Types; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; From fd2d88d1470782dff9865d07df6347f1e35ab0bc Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Fri, 29 Oct 2021 14:38:08 +0200 Subject: [PATCH 349/455] improve DI capabilities of GraphQLLocalExecutionClient --- .../GraphQL.Client.LocalExecution.csproj | 3 +- .../GraphQLLocalExecutionClient.cs | 33 +++++++++---------- .../ServiceCollectionExtensions.cs | 18 ++++++++++ 3 files changed, 36 insertions(+), 18 deletions(-) create mode 100644 src/GraphQL.Client.LocalExecution/ServiceCollectionExtensions.cs diff --git a/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj b/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj index d69fc21b..00ef36b3 100644 --- a/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj +++ b/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj @@ -8,8 +8,9 @@ - + + diff --git a/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs b/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs index f2c077f6..624526f3 100644 --- a/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs +++ b/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs @@ -21,7 +21,7 @@ namespace GraphQL.Client.LocalExecution public static class GraphQLLocalExecutionClient { public static GraphQLLocalExecutionClient New(TSchema schema, IGraphQLJsonSerializer serializer) where TSchema : ISchema - => new GraphQLLocalExecutionClient(schema, serializer); + => new GraphQLLocalExecutionClient(schema, serializer, new SubscriptionDocumentExecuter(), new DocumentWriter()); } public class GraphQLLocalExecutionClient : IGraphQLClient where TSchema : ISchema @@ -41,18 +41,18 @@ public class GraphQLLocalExecutionClient : IGraphQLClient where TSchema public IGraphQLJsonSerializer Serializer { get; } - private readonly DocumentExecuter _documentExecuter; - private readonly DocumentWriter _documentWriter; + private readonly IDocumentExecuter _documentExecuter; + private readonly IDocumentWriter _documentWriter; - public GraphQLLocalExecutionClient(TSchema schema, IGraphQLJsonSerializer serializer) + public GraphQLLocalExecutionClient(TSchema schema, IGraphQLJsonSerializer serializer, IDocumentExecuter documentExecuter, IDocumentWriter documentWriter) { Schema = schema ?? throw new ArgumentNullException(nameof(schema), "no schema configured"); Serializer = serializer ?? throw new ArgumentNullException(nameof(serializer), "please configure the JSON serializer you want to use"); if (!Schema.Initialized) Schema.Initialize(); - _documentExecuter = new DocumentExecuter(); - _documentWriter = new DocumentWriter(); + _documentExecuter = documentExecuter; + _documentWriter = documentWriter; } public void Dispose() { } @@ -78,7 +78,7 @@ public IObservable> CreateSubscriptionStream> ExecuteQueryAsync(GraphQLRequest request, CancellationToken cancellationToken) { var executionResult = await ExecuteAsync(request, cancellationToken); - return await ExecutionResultToGraphQLResponse(executionResult, cancellationToken); + return await ExecutionResultToGraphQLResponseAsync(executionResult, cancellationToken); } private async Task>> ExecuteSubscriptionAsync(GraphQLRequest request, CancellationToken cancellationToken = default) { @@ -87,12 +87,12 @@ private async Task>> ExecuteSubscriptionA return stream == null ? Observable.Throw>(new InvalidOperationException("the GraphQL execution did not return an observable")) - : stream.SelectMany(executionResult => Observable.FromAsync(token => ExecutionResultToGraphQLResponse(executionResult, token))); + : stream.SelectMany(executionResult => Observable.FromAsync(token => ExecutionResultToGraphQLResponseAsync(executionResult, token))); } private async Task ExecuteAsync(GraphQLRequest request, CancellationToken cancellationToken = default) { - var serializedRequest = Serializer.SerializeToString(request); + string serializedRequest = Serializer.SerializeToString(request); var deserializedRequest = JsonConvert.DeserializeObject(serializedRequest); var inputs = deserializedRequest.Variables != null @@ -103,8 +103,8 @@ private async Task ExecuteAsync(GraphQLRequest request, Cancell var result = await _documentExecuter.ExecuteAsync(options => { options.Schema = Schema; - options.OperationName = request.OperationName; - options.Query = request.Query; + options.OperationName = deserializedRequest?.OperationName; + options.Query = deserializedRequest?.Query; options.Inputs = inputs; options.CancellationToken = cancellationToken; }); @@ -112,13 +112,12 @@ private async Task ExecuteAsync(GraphQLRequest request, Cancell return result; } - private async Task> ExecutionResultToGraphQLResponse(ExecutionResult executionResult, CancellationToken cancellationToken = default) + private async Task> ExecutionResultToGraphQLResponseAsync(ExecutionResult executionResult, CancellationToken cancellationToken = default) { - string json = await _documentWriter.WriteToStringAsync(executionResult); - // serialize result into utf8 byte stream - var resultStream = new MemoryStream(Encoding.UTF8.GetBytes(json)); - // deserialize using the provided serializer - return await Serializer.DeserializeFromUtf8StreamAsync(resultStream, cancellationToken); + using var stream = new MemoryStream(); + await _documentWriter.WriteAsync(stream, executionResult, cancellationToken); + stream.Seek(0, SeekOrigin.Begin); + return await Serializer.DeserializeFromUtf8StreamAsync(stream, cancellationToken); } #endregion diff --git a/src/GraphQL.Client.LocalExecution/ServiceCollectionExtensions.cs b/src/GraphQL.Client.LocalExecution/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..3d961d14 --- /dev/null +++ b/src/GraphQL.Client.LocalExecution/ServiceCollectionExtensions.cs @@ -0,0 +1,18 @@ +using GraphQL.Client.Abstractions; +using GraphQL.DI; +using GraphQL.MicrosoftDI; +using GraphQL.Types; +using Microsoft.Extensions.DependencyInjection; + +namespace GraphQL.Client.LocalExecution +{ + public static class ServiceCollectionExtensions + { + public static IGraphQLBuilder AddGraphQLLocalExecutionClient(this IServiceCollection services) where TSchema : ISchema + { + services.AddSingleton>(); + services.AddSingleton>(); + return services.AddGraphQL(); + } + } +} From 7cb148e98ecc5b6c34d05c714f161a7fdc008e76 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Fri, 29 Oct 2021 14:40:28 +0200 Subject: [PATCH 350/455] fix preprocessing of queries and mutations when sent via websocket --- .../GraphQLLocalExecutionClient.cs | 1 - .../Websocket/GraphQLHttpWebSocket.cs | 98 +++++++++---------- 2 files changed, 49 insertions(+), 50 deletions(-) diff --git a/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs b/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs index 624526f3..bd2b54fa 100644 --- a/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs +++ b/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Reactive.Linq; using System.Reactive.Threading.Tasks; -using System.Text; using System.Threading; using System.Threading.Tasks; using GraphQL.Client.Abstractions; diff --git a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs index ec4ac4b7..9f12d5bf 100644 --- a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs +++ b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs @@ -182,7 +182,7 @@ public IObservable> CreateSubscriptionStream> CreateSubscriptionStream public Task> SendRequest(GraphQLRequest request, CancellationToken cancellationToken = default) => Observable.Create>(async observer => + { + var preprocessedRequest = await _client.Options.PreprocessRequest(request, _client); + var websocketRequest = new GraphQLWebSocketRequest { - await _client.Options.PreprocessRequest(request, _client); - var websocketRequest = new GraphQLWebSocketRequest + Id = Guid.NewGuid().ToString("N"), + Type = GraphQLWebSocketMessageType.GQL_START, + Payload = preprocessedRequest + }; + var observable = IncomingMessageStream + .Where(response => response != null && response.Id == websocketRequest.Id) + .TakeUntil(response => response.Type == GraphQLWebSocketMessageType.GQL_COMPLETE) + .Select(response => { - Id = Guid.NewGuid().ToString("N"), - Type = GraphQLWebSocketMessageType.GQL_START, - Payload = request - }; - var observable = IncomingMessageStream - .Where(response => response != null && response.Id == websocketRequest.Id) - .TakeUntil(response => response.Type == GraphQLWebSocketMessageType.GQL_COMPLETE) - .Select(response => - { - Debug.WriteLine($"received response for request {websocketRequest.Id}"); - var typedResponse = - _client.JsonSerializer.DeserializeToWebsocketResponse( - response.MessageBytes); - return typedResponse.Payload; - }); + Debug.WriteLine($"received response for request {websocketRequest.Id}"); + var typedResponse = + _client.JsonSerializer.DeserializeToWebsocketResponse( + response.MessageBytes); + return typedResponse.Payload; + }); - try - { - // initialize websocket (completes immediately if socket is already open) - await InitializeWebSocket(); - } - catch (Exception e) - { - // subscribe observer to failed observable - return Observable.Throw>(e).Subscribe(observer); - } + try + { + // initialize websocket (completes immediately if socket is already open) + await InitializeWebSocket(); + } + catch (Exception e) + { + // subscribe observer to failed observable + return Observable.Throw>(e).Subscribe(observer); + } - var disposable = new CompositeDisposable( - observable.Subscribe(observer) - ); + var disposable = new CompositeDisposable( + observable.Subscribe(observer) + ); - Debug.WriteLine($"submitting request {websocketRequest.Id}"); - // send request - try - { - await QueueWebSocketRequest(websocketRequest); - } - catch (Exception e) - { - Debug.WriteLine(e); - throw; - } + Debug.WriteLine($"submitting request {websocketRequest.Id}"); + // send request + try + { + await QueueWebSocketRequest(websocketRequest); + } + catch (Exception e) + { + Debug.WriteLine(e); + throw; + } - return disposable; - }) + return disposable; + }) // complete sequence on OperationCanceledException, this is triggered by the cancellation token .Catch, OperationCanceledException>(exception => Observable.Empty>()) @@ -410,7 +410,7 @@ public Task InitializeWebSocket() } catch (NotImplementedException) { - Debug.WriteLine("property 'ClientWebSocketOptions.ClientCertificates' not implemented by current platform"); + Debug.WriteLine("property 'ClientWebSocketOptions.ClientCertificates' not implemented by current platform"); } catch (PlatformNotSupportedException) { @@ -423,7 +423,7 @@ public Task InitializeWebSocket() } catch (NotImplementedException) { - Debug.WriteLine("property 'ClientWebSocketOptions.UseDefaultCredentials' not implemented by current platform"); + Debug.WriteLine("property 'ClientWebSocketOptions.UseDefaultCredentials' not implemented by current platform"); } catch (PlatformNotSupportedException) { @@ -479,7 +479,7 @@ private async Task ConnectAsync(CancellationToken token) Debug.WriteLine($"new incoming message stream {_incomingMessages.GetHashCode()} created"); _incomingMessagesConnection = new CompositeDisposable(maintenanceSubscription, connection); - + var initRequest = new GraphQLWebSocketRequest { Type = GraphQLWebSocketMessageType.GQL_CONNECTION_INIT, @@ -488,7 +488,7 @@ private async Task ConnectAsync(CancellationToken token) // setup task to await connection_ack message var ackTask = _incomingMessages - .Where(response => response != null ) + .Where(response => response != null) .TakeUntil(response => response.Type == GraphQLWebSocketMessageType.GQL_CONNECTION_ACK || response.Type == GraphQLWebSocketMessageType.GQL_CONNECTION_ERROR) .LastAsync() @@ -640,7 +640,7 @@ private async Task CloseAsync() } Debug.WriteLine($"send \"connection_terminate\" message"); - await SendWebSocketMessageAsync(new GraphQLWebSocketRequest{Type = GraphQLWebSocketMessageType.GQL_CONNECTION_TERMINATE}); + await SendWebSocketMessageAsync(new GraphQLWebSocketRequest { Type = GraphQLWebSocketMessageType.GQL_CONNECTION_TERMINATE }); Debug.WriteLine($"closing websocket {_clientWebSocket.GetHashCode()}"); await _clientWebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None); From e3f8ad6306188887829894d6a143f7a606589b6a Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Fri, 29 Oct 2021 19:29:20 +0300 Subject: [PATCH 351/455] Fix for double instantiation of git@github.com:graphql-dotnet/graphql-client.git --- .../ServiceCollectionExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GraphQL.Client.LocalExecution/ServiceCollectionExtensions.cs b/src/GraphQL.Client.LocalExecution/ServiceCollectionExtensions.cs index 3d961d14..0fe7b234 100644 --- a/src/GraphQL.Client.LocalExecution/ServiceCollectionExtensions.cs +++ b/src/GraphQL.Client.LocalExecution/ServiceCollectionExtensions.cs @@ -11,7 +11,7 @@ public static class ServiceCollectionExtensions public static IGraphQLBuilder AddGraphQLLocalExecutionClient(this IServiceCollection services) where TSchema : ISchema { services.AddSingleton>(); - services.AddSingleton>(); + services.AddSingleton(p => p.GetRequiredService>()); return services.AddGraphQL(); } } From 2fcfc086e319dc439a28e9812c6965fb2dbdd01d Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Mon, 1 Nov 2021 21:46:22 +0100 Subject: [PATCH 352/455] upgrade test dependencies, implement ConfigureAwait(false) in libs --- .editorconfig | 3 + GraphQL.Client.sln | 6 + examples/.editorconfig | 2 + .../GraphQLLocalExecutionClient.cs | 12 +- src/GraphQL.Client/GraphQLHttpClient.cs | 14 +- .../Websocket/GraphQLHttpWebSocket.cs | 44 +-- .../.editorconfig | 2 + .../FluentTestObserver.cs | 124 ------- .../NoSynchronizationContextScope.cs | 40 --- .../ReactiveAssertions.cs | 309 ------------------ .../ReactiveExtensions.cs | 108 ------ .../RollingReplaySubject.cs | 64 ---- .../GraphQL.Client.Tests.Common.csproj | 3 +- .../Helpers/CallbackMonitor.cs | 6 +- tests/GraphQL.Integration.Tests/.editorconfig | 2 + .../GraphQL.Integration.Tests.csproj | 4 +- .../QueryAndMutationTests/Base.cs | 6 +- .../WebsocketTests/Base.cs | 4 +- tests/GraphQL.Primitives.Tests/.editorconfig | 2 + tests/GraphQL.Server.Test/.editorconfig | 2 + tests/IntegrationTestServer/.editorconfig | 2 + tests/tests.props | 8 +- 22 files changed, 70 insertions(+), 697 deletions(-) create mode 100644 examples/.editorconfig create mode 100644 tests/GraphQL.Client.Serializer.Tests/.editorconfig delete mode 100644 tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/FluentTestObserver.cs delete mode 100644 tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/NoSynchronizationContextScope.cs delete mode 100644 tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ReactiveAssertions.cs delete mode 100644 tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ReactiveExtensions.cs delete mode 100644 tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/RollingReplaySubject.cs create mode 100644 tests/GraphQL.Integration.Tests/.editorconfig create mode 100644 tests/GraphQL.Primitives.Tests/.editorconfig create mode 100644 tests/GraphQL.Server.Test/.editorconfig create mode 100644 tests/IntegrationTestServer/.editorconfig diff --git a/.editorconfig b/.editorconfig index 8f9adfad..33d7fdc4 100644 --- a/.editorconfig +++ b/.editorconfig @@ -254,3 +254,6 @@ dotnet_naming_style.end_in_async_style.required_suffix = Async # dotnet_naming_rule..severity = dotnet_naming_rule.async_methods_end_in_async.severity = warning + +# ReSharper: Configure await +configure_await_analysis_mode = library diff --git a/GraphQL.Client.sln b/GraphQL.Client.sln index ab95f2ec..01a7ad88 100644 --- a/GraphQL.Client.sln +++ b/GraphQL.Client.sln @@ -5,6 +5,7 @@ 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 + src\.editorconfig = src\.editorconfig src\src.props = src\src.props EndProjectSection EndProject @@ -14,6 +15,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .gitignore = .gitignore dotnet-tools.json = dotnet-tools.json LICENSE.txt = LICENSE.txt + examples\GraphQL.Client.Example\Program.cs = examples\GraphQL.Client.Example\Program.cs README.md = README.md root.props = root.props EndProjectSection @@ -65,6 +67,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{89 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphQL.Client.Example", "examples\GraphQL.Client.Example\GraphQL.Client.Example.csproj", "{6B13B87D-1EF4-485F-BC5D-891E2F4DA6CD}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{98D4DDDD-DE15-4997-B888-9BC806C7416C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -133,6 +137,7 @@ Global EndGlobalSection GlobalSection(NestedProjects) = preSolution {E95A1258-F666-4D4E-9101-E0C46F6A3CB3} = {0B0EDB0F-FF67-4B78-A8DB-B5C23E1FEE8C} + {05CAF9B2-981E-40C0-AE31-5FA56E351F12} = {98D4DDDD-DE15-4997-B888-9BC806C7416C} {87FC440E-6A4D-47D8-9EB2-416FC31CC4A6} = {47C98B55-08F1-4428-863E-2C5C876DEEFE} {C212983F-67DB-44EB-BFB0-5DA75A86DF55} = {0B0EDB0F-FF67-4B78-A8DB-B5C23E1FEE8C} {92107DF5-73DF-4371-8EB1-6734FED704AD} = {0B0EDB0F-FF67-4B78-A8DB-B5C23E1FEE8C} @@ -146,6 +151,7 @@ Global {0D307BAD-27AE-4A5D-8764-4AA2620B01E9} = {0B0EDB0F-FF67-4B78-A8DB-B5C23E1FEE8C} {7FFFEC00-D751-4FFC-9FD4-E91858F9A1C5} = {47C98B55-08F1-4428-863E-2C5C876DEEFE} {6B13B87D-1EF4-485F-BC5D-891E2F4DA6CD} = {89AD33AB-64F6-4F82-822F-21DF7A10CEC0} + {98D4DDDD-DE15-4997-B888-9BC806C7416C} = {63F75859-4698-4EDE-8B70-4ACBB8BC425A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {387AC1AC-F90C-4EF8-955A-04D495C75AF4} diff --git a/examples/.editorconfig b/examples/.editorconfig new file mode 100644 index 00000000..d1655ff8 --- /dev/null +++ b/examples/.editorconfig @@ -0,0 +1,2 @@ +# Configure await +configure_await_analysis_mode = disabled diff --git a/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs b/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs index bd2b54fa..889bc991 100644 --- a/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs +++ b/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs @@ -76,12 +76,12 @@ public IObservable> CreateSubscriptionStream> ExecuteQueryAsync(GraphQLRequest request, CancellationToken cancellationToken) { - var executionResult = await ExecuteAsync(request, cancellationToken); - return await ExecutionResultToGraphQLResponseAsync(executionResult, cancellationToken); + var executionResult = await ExecuteAsync(request, cancellationToken).ConfigureAwait(false); + return await ExecutionResultToGraphQLResponseAsync(executionResult, cancellationToken).ConfigureAwait(false); } private async Task>> ExecuteSubscriptionAsync(GraphQLRequest request, CancellationToken cancellationToken = default) { - var result = await ExecuteAsync(request, cancellationToken); + var result = await ExecuteAsync(request, cancellationToken).ConfigureAwait(false); var stream = ((SubscriptionExecutionResult)result).Streams?.Values.SingleOrDefault(); return stream == null @@ -106,7 +106,7 @@ private async Task ExecuteAsync(GraphQLRequest request, Cancell options.Query = deserializedRequest?.Query; options.Inputs = inputs; options.CancellationToken = cancellationToken; - }); + }).ConfigureAwait(false); return result; } @@ -114,9 +114,9 @@ private async Task ExecuteAsync(GraphQLRequest request, Cancell private async Task> ExecutionResultToGraphQLResponseAsync(ExecutionResult executionResult, CancellationToken cancellationToken = default) { using var stream = new MemoryStream(); - await _documentWriter.WriteAsync(stream, executionResult, cancellationToken); + await _documentWriter.WriteAsync(stream, executionResult, cancellationToken).ConfigureAwait(false); stream.Seek(0, SeekOrigin.Begin); - return await Serializer.DeserializeFromUtf8StreamAsync(stream, cancellationToken); + return await Serializer.DeserializeFromUtf8StreamAsync(stream, cancellationToken).ConfigureAwait(false); } #endregion diff --git a/src/GraphQL.Client/GraphQLHttpClient.cs b/src/GraphQL.Client/GraphQLHttpClient.cs index 9d2aac47..cd51622c 100644 --- a/src/GraphQL.Client/GraphQLHttpClient.cs +++ b/src/GraphQL.Client/GraphQLHttpClient.cs @@ -85,9 +85,9 @@ public async Task> SendQueryAsync(GraphQLR if (Options.UseWebSocketForQueriesAndMutations || !(Options.WebSocketEndPoint is null) && Options.EndPoint is null || Options.EndPoint.HasWebSocketScheme()) - return await GraphQlHttpWebSocket.SendRequest(request, cancellationToken); + return await GraphQlHttpWebSocket.SendRequest(request, cancellationToken).ConfigureAwait(false); - return await SendHttpRequestAsync(request, cancellationToken); + return await SendHttpRequestAsync(request, cancellationToken).ConfigureAwait(false); } /// @@ -140,16 +140,16 @@ public IObservable> CreateSubscriptionStream> SendHttpRequestAsync(GraphQLRequest request, CancellationToken cancellationToken = default) { - var preprocessedRequest = await Options.PreprocessRequest(request, this); + var preprocessedRequest = await Options.PreprocessRequest(request, this).ConfigureAwait(false); using var httpRequestMessage = preprocessedRequest.ToHttpRequestMessage(Options, JsonSerializer); - using var httpResponseMessage = await HttpClient.SendAsync(httpRequestMessage, HttpCompletionOption.ResponseHeadersRead, cancellationToken); + using var httpResponseMessage = await HttpClient.SendAsync(httpRequestMessage, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); - var contentStream = await httpResponseMessage.Content.ReadAsStreamAsync(); + var contentStream = await httpResponseMessage.Content.ReadAsStreamAsync().ConfigureAwait(false); if (httpResponseMessage.IsSuccessStatusCode) { - var graphQLResponse = await JsonSerializer.DeserializeFromUtf8StreamAsync(contentStream, cancellationToken); + var graphQLResponse = await JsonSerializer.DeserializeFromUtf8StreamAsync(contentStream, cancellationToken).ConfigureAwait(false); return graphQLResponse.ToGraphQLHttpResponse(httpResponseMessage.Headers, httpResponseMessage.StatusCode); } @@ -157,7 +157,7 @@ private async Task> SendHttpRequestAsync> CreateSubscriptionStream>(async observer => { Debug.WriteLine($"Create observable thread id: {Thread.CurrentThread.ManagedThreadId}"); - var preprocessedRequest = await _client.Options.PreprocessRequest(request, _client); + var preprocessedRequest = await _client.Options.PreprocessRequest(request, _client).ConfigureAwait(false); var startRequest = new GraphQLWebSocketRequest { @@ -157,7 +157,7 @@ public IObservable> CreateSubscriptionStream> CreateSubscriptionStream> CreateSubscriptionStream> CreateSubscriptionStream> SendRequest(GraphQLRequest request, CancellationToken cancellationToken = default) => Observable.Create>(async observer => { - var preprocessedRequest = await _client.Options.PreprocessRequest(request, _client); + var preprocessedRequest = await _client.Options.PreprocessRequest(request, _client).ConfigureAwait(false); var websocketRequest = new GraphQLWebSocketRequest { Id = Guid.NewGuid().ToString("N"), @@ -288,7 +288,7 @@ public Task> SendRequest(GraphQLRequest re try { // initialize websocket (completes immediately if socket is already open) - await InitializeWebSocket(); + await InitializeWebSocket().ConfigureAwait(false); } catch (Exception e) { @@ -304,7 +304,7 @@ public Task> SendRequest(GraphQLRequest re // send request try { - await QueueWebSocketRequest(websocketRequest); + await QueueWebSocketRequest(websocketRequest).ConfigureAwait(false); } catch (Exception e) { @@ -336,8 +336,8 @@ private async Task SendWebSocketRequestAsync(GraphQLWebSocketRequest reque return Unit.Default; } - await InitializeWebSocket(); - await SendWebSocketMessageAsync(request, _internalCancellationToken); + await InitializeWebSocket().ConfigureAwait(false); + await SendWebSocketMessageAsync(request, _internalCancellationToken).ConfigureAwait(false); request.SendCompleted(); } catch (Exception e) @@ -354,7 +354,7 @@ await _clientWebSocket.SendAsync( new ArraySegment(requestBytes), WebSocketMessageType.Text, true, - cancellationToken); + cancellationToken).ConfigureAwait(false); } #endregion @@ -440,13 +440,13 @@ private async Task ConnectAsync(CancellationToken token) { try { - await BackOff(); + await BackOff().ConfigureAwait(false); _stateSubject.OnNext(GraphQLWebsocketConnectionState.Connecting); Debug.WriteLine($"opening websocket {_clientWebSocket.GetHashCode()} (thread {Thread.CurrentThread.ManagedThreadId})"); - await _clientWebSocket.ConnectAsync(_webSocketUri, token); + await _clientWebSocket.ConnectAsync(_webSocketUri, token).ConfigureAwait(false); _stateSubject.OnNext(GraphQLWebsocketConnectionState.Connected); Debug.WriteLine($"connection established on websocket {_clientWebSocket.GetHashCode()}, invoking Options.OnWebsocketConnected()"); - await (Options.OnWebsocketConnected?.Invoke(_client) ?? Task.CompletedTask); + await (Options.OnWebsocketConnected?.Invoke(_client) ?? Task.CompletedTask).ConfigureAwait(false); Debug.WriteLine($"invoking Options.OnWebsocketConnected() on websocket {_clientWebSocket.GetHashCode()}"); _connectionAttempt = 1; @@ -496,8 +496,8 @@ private async Task ConnectAsync(CancellationToken token) // send connection init Debug.WriteLine($"sending connection init message"); - await SendWebSocketMessageAsync(initRequest); - var response = await ackTask; + await SendWebSocketMessageAsync(initRequest).ConfigureAwait(false); + var response = await ackTask.ConfigureAwait(false); if (response.Type == GraphQLWebSocketMessageType.GQL_CONNECTION_ACK) Debug.WriteLine($"connection acknowledged: {Encoding.UTF8.GetString(response.MessageBytes)}"); @@ -537,7 +537,7 @@ private IObservable GetMessageStream() => Observable.Create(async observer => { // make sure the websocket is connected - await InitializeWebSocket(); + await InitializeWebSocket().ConfigureAwait(false); // subscribe observer to message stream var subscription = new CompositeDisposable(_incomingMessages .Subscribe(observer)) @@ -591,7 +591,7 @@ private async Task ReceiveWebsocketMessagesAsync() do { // cancellation is done implicitly via the close method - webSocketReceiveResult = await _clientWebSocket.ReceiveAsync(_buffer, CancellationToken.None); + webSocketReceiveResult = await _clientWebSocket.ReceiveAsync(_buffer, CancellationToken.None).ConfigureAwait(false); ms.Write(_buffer.Array, _buffer.Offset, webSocketReceiveResult.Count); } while (!webSocketReceiveResult.EndOfMessage && !_internalCancellationToken.IsCancellationRequested); @@ -602,13 +602,13 @@ private async Task ReceiveWebsocketMessagesAsync() switch (webSocketReceiveResult.MessageType) { case WebSocketMessageType.Text: - var response = await _client.JsonSerializer.DeserializeToWebsocketResponseWrapperAsync(ms); + var response = await _client.JsonSerializer.DeserializeToWebsocketResponseWrapperAsync(ms).ConfigureAwait(false); response.MessageBytes = ms.ToArray(); Debug.WriteLine($"{response.MessageBytes.Length} bytes received for id {response.Id} on websocket {_clientWebSocket.GetHashCode()} (thread {Thread.CurrentThread.ManagedThreadId})..."); return response; case WebSocketMessageType.Close: - var closeResponse = await _client.JsonSerializer.DeserializeToWebsocketResponseWrapperAsync(ms); + var closeResponse = await _client.JsonSerializer.DeserializeToWebsocketResponseWrapperAsync(ms).ConfigureAwait(false); closeResponse.MessageBytes = ms.ToArray(); Debug.WriteLine($"Connection closed by the server."); throw new Exception("Connection closed by the server."); @@ -640,10 +640,10 @@ private async Task CloseAsync() } Debug.WriteLine($"send \"connection_terminate\" message"); - await SendWebSocketMessageAsync(new GraphQLWebSocketRequest { Type = GraphQLWebSocketMessageType.GQL_CONNECTION_TERMINATE }); + await SendWebSocketMessageAsync(new GraphQLWebSocketRequest { Type = GraphQLWebSocketMessageType.GQL_CONNECTION_TERMINATE }).ConfigureAwait(false); Debug.WriteLine($"closing websocket {_clientWebSocket.GetHashCode()}"); - await _clientWebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None); + await _clientWebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None).ConfigureAwait(false); _stateSubject.OnNext(GraphQLWebsocketConnectionState.Disconnected); } @@ -679,7 +679,7 @@ private async Task CompleteAsync() if (!_internalCancellationTokenSource.IsCancellationRequested) _internalCancellationTokenSource.Cancel(); - await CloseAsync(); + await CloseAsync().ConfigureAwait(false); _requestSubscription?.Dispose(); _clientWebSocket?.Dispose(); diff --git a/tests/GraphQL.Client.Serializer.Tests/.editorconfig b/tests/GraphQL.Client.Serializer.Tests/.editorconfig new file mode 100644 index 00000000..d1655ff8 --- /dev/null +++ b/tests/GraphQL.Client.Serializer.Tests/.editorconfig @@ -0,0 +1,2 @@ +# Configure await +configure_await_analysis_mode = disabled diff --git a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/FluentTestObserver.cs b/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/FluentTestObserver.cs deleted file mode 100644 index 5b494159..00000000 --- a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/FluentTestObserver.cs +++ /dev/null @@ -1,124 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using Microsoft.Reactive.Testing; - -namespace GraphQL.Client.Tests.Common.FluentAssertions.Reactive -{ - /// - /// Observer for testing s using the FluentAssertions framework - /// - /// - public class FluentTestObserver : IObserver, IDisposable - { - private readonly IDisposable _subscription; - private readonly IScheduler _observeScheduler; - private readonly RollingReplaySubject>> _rollingReplaySubject = new RollingReplaySubject>>(); - - /// - /// The observable which is observed by this instance - /// - public IObservable Subject { get; } - - /// - /// The stream of recorded s - /// - public IObservable>> RecordedNotificationStream => _rollingReplaySubject.AsObservable(); - - /// - /// The recorded s - /// - public IEnumerable>> RecordedNotifications => - _rollingReplaySubject.GetSnapshot(); - - /// - /// The recorded messages - /// - public IEnumerable RecordedMessages => - RecordedNotifications.GetMessages(); - - /// - /// The exception - /// - public Exception Error => - RecordedNotifications - .Where(r => r.Value.Kind == NotificationKind.OnError) - .Select(r => r.Value.Exception) - .FirstOrDefault(); - - /// - /// The recorded messages - /// - public bool Completed => - RecordedNotifications - .Any(r => r.Value.Kind == NotificationKind.OnCompleted); - - /// - /// Creates a new which subscribes to the supplied - /// - /// the under test - public FluentTestObserver(IObservable subject) - { - Subject = subject; - _observeScheduler = new EventLoopScheduler(); - _subscription = subject.ObserveOn(_observeScheduler).Subscribe(this); - } - - /// - /// Creates a new which subscribes to the supplied - /// - /// the under test - public FluentTestObserver(IObservable subject, IScheduler scheduler) - { - Subject = subject; - _observeScheduler = scheduler; - _subscription = subject.ObserveOn(scheduler).Subscribe(this); - } - - /// - /// Creates a new which subscribes to the supplied - /// - /// the under test - public FluentTestObserver(IObservable subject, TestScheduler testScheduler) - { - Subject = subject; - _observeScheduler = testScheduler; - _subscription = subject.ObserveOn(Scheduler.CurrentThread).Subscribe(this); - } - - /// - /// Clears the recorded notifications and messages as well as the recorded notifications stream buffer - /// - public void Clear() => _rollingReplaySubject.Clear(); - - /// - public void OnNext(TPayload value) => - _rollingReplaySubject.OnNext(new Recorded>(_observeScheduler.Now.UtcTicks, Notification.CreateOnNext(value))); - - /// - public void OnError(Exception exception) => - _rollingReplaySubject.OnNext(new Recorded>(_observeScheduler.Now.UtcTicks, Notification.CreateOnError(exception))); - - /// - public void OnCompleted() => - _rollingReplaySubject.OnNext(new Recorded>(_observeScheduler.Now.UtcTicks, Notification.CreateOnCompleted())); - - /// - public void Dispose() - { - _subscription?.Dispose(); - _rollingReplaySubject?.Dispose(); - } - - /// - /// Returns an object that can be used to assert the observed - /// - /// - public ReactiveAssertions Should() => new ReactiveAssertions(this); - } -} diff --git a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/NoSynchronizationContextScope.cs b/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/NoSynchronizationContextScope.cs deleted file mode 100644 index c728a671..00000000 --- a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/NoSynchronizationContextScope.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace GraphQL.Client.Tests.Common.FluentAssertions.Reactive -{ - internal static class NoSynchronizationContextScope - { - public static T ExecuteInDefaultSynchronizationContext(this Task task) - { - using (NoSynchronizationContextScope.Enter()) - { - task.Wait(); - return task.Result; - } - } - - public static DisposingAction Enter() - { - var context = SynchronizationContext.Current; - SynchronizationContext.SetSynchronizationContext(null); - return new DisposingAction(() => SynchronizationContext.SetSynchronizationContext(context)); - } - - internal class DisposingAction : IDisposable - { - private readonly Action action; - - public DisposingAction(Action action) - { - this.action = action; - } - - public void Dispose() - { - action(); - } - } - } -} diff --git a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ReactiveAssertions.cs b/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ReactiveAssertions.cs deleted file mode 100644 index b9aa410c..00000000 --- a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ReactiveAssertions.cs +++ /dev/null @@ -1,309 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive; -using System.Reactive.Linq; -using System.Reactive.Threading.Tasks; -using System.Threading.Tasks; -using FluentAssertions; -using FluentAssertions.Execution; -using FluentAssertions.Primitives; -using FluentAssertions.Specialized; -using Microsoft.Reactive.Testing; - -namespace GraphQL.Client.Tests.Common.FluentAssertions.Reactive -{ - /// - /// Provides methods to assert an observed by a - /// - /// - public class ReactiveAssertions : ReferenceTypeAssertions, ReactiveAssertions> - { - private readonly IExtractExceptions extractor = new AggregateExceptionExtractor(); - public FluentTestObserver Observer { get; } - - protected internal ReactiveAssertions(FluentTestObserver observer): base(observer.Subject) - { - Observer = observer; - } - - protected override string Identifier => "Subscription"; - - /// - /// Asserts that at least notifications were pushed to the within the specified .
- /// This includes any previously recorded notifications since it has been created or cleared. - ///
- /// the number of notifications the observer should have recorded by now - /// the maximum time to wait for the notifications to arrive - /// - /// - public AndWhichConstraint, IEnumerable> Push(int numberOfNotifications, TimeSpan timeout, - string because = "", params object[] becauseArgs) - { - IList notifications = new List(); - var assertion = Execute.Assertion - .WithExpectation($"Expected observable to push at least {numberOfNotifications} {(numberOfNotifications == 1 ? "notification" : "notifications")}, ") - .BecauseOf(because, becauseArgs); - - try - { - notifications = Observer.RecordedNotificationStream - .Select(r => r.Value) - .Dematerialize() - .Take(numberOfNotifications) - .Timeout(timeout) - .Catch(exception => Observable.Empty()) - .ToList() - .ToTask() - .ExecuteInDefaultSynchronizationContext(); - } - catch (Exception e) - { - if(e is AggregateException aggregateException) - e = aggregateException.InnerException; - assertion.FailWith("but it failed with a {0}.", e); - } - - assertion - .ForCondition(notifications.Count >= numberOfNotifications) - .FailWith("but {0} were received within {1}.", notifications.Count, timeout); - - return new AndWhichConstraint, IEnumerable>(this, notifications); - } - - /// - public async Task, IEnumerable>> PushAsync(int numberOfNotifications, TimeSpan timeout, - string because = "", params object[] becauseArgs) - { - IList notifications = new List(); - var assertion = Execute.Assertion - .WithExpectation($"Expected observable to push at least {numberOfNotifications} {(numberOfNotifications == 1 ? "notification" : "notifications")}, ") - .BecauseOf(because, becauseArgs); - - try - { - notifications = await Observer.RecordedNotificationStream - .Select(r => r.Value) - .Dematerialize() - .Take(numberOfNotifications) - .Timeout(timeout) - .Catch(exception => Observable.Empty()) - .ToList() - .ToTask(); - } - catch (Exception e) - { - if (e is AggregateException aggregateException) - e = aggregateException.InnerException; - assertion.FailWith("but it failed with a {0}.", e); - } - - assertion - .ForCondition(notifications.Count >= numberOfNotifications) - .FailWith("but {0} were received within {1}.", notifications.Count, timeout); - - return new AndWhichConstraint, IEnumerable>(this, notifications); - } - - /// - /// Asserts that at least notifications are pushed to the within the next 1 second.
- /// This includes any previously recorded notifications since it has been created or cleared. - ///
- /// the number of notifications the observer should have recorded by now - /// - /// - public AndWhichConstraint, IEnumerable> Push(int numberOfNotifications, string because = "", params object[] becauseArgs) - => Push(numberOfNotifications, TimeSpan.FromSeconds(10), because, becauseArgs); - - /// - public Task, IEnumerable>> PushAsync(int numberOfNotifications, string because = "", params object[] becauseArgs) - => PushAsync(numberOfNotifications, TimeSpan.FromSeconds(10), because, becauseArgs); - - /// - /// Asserts that at least 1 notification is pushed to the within the next 1 second.
- /// This includes any previously recorded notifications since it has been created or cleared. - ///
- public AndWhichConstraint, IEnumerable> Push(string because = "", params object[] becauseArgs) - => Push(1, TimeSpan.FromSeconds(1), because, becauseArgs); - - /// - public Task, IEnumerable>> PushAsync(string because = "", params object[] becauseArgs) - => PushAsync(1, TimeSpan.FromSeconds(1), because, becauseArgs); - - /// - /// Asserts that the does not receive any notifications within the specified .
- /// This includes any previously recorded notifications since it has been created or cleared. - ///
- public AndConstraint> NotPush(TimeSpan timeout, - string because = "", params object[] becauseArgs) - { - bool anyNotifications = Observer.RecordedNotificationStream - .Any(recorded => recorded.Value.Kind == NotificationKind.OnNext) - .Timeout(timeout) - .Catch(Observable.Return(false)) - .ToTask() - .ExecuteInDefaultSynchronizationContext(); - - Execute.Assertion - .ForCondition(!anyNotifications) - .BecauseOf(because, becauseArgs) - .FailWith("Expected observable to not push any notifications{reason}, but it did."); - - return new AndConstraint>(this); - } - - /// - /// Asserts that the does not receive any notifications within the next 100 milliseconds.
- /// This includes any previously recorded notifications since it has been created or last cleared. - ///
- public AndConstraint> NotPush(string because = "", params object[] becauseArgs) - => NotPush(TimeSpan.FromMilliseconds(100), because, becauseArgs); - - /// - /// Asserts that the observed by the fails within the specified . - /// - public ExceptionAssertions Throw(TimeSpan timeout, string because = "", params object[] becauseArgs) - where TException : Exception - { - var notifications = GetRecordedNotifications(timeout).ExecuteInDefaultSynchronizationContext(); - return Throw(notifications, because, becauseArgs); - } - - /// - public async Task> ThrowAsync(TimeSpan timeout, - string because = "", params object[] becauseArgs) - where TException : Exception - { - var notifications = await GetRecordedNotifications(timeout); - return Throw(notifications, because, becauseArgs); - } - - /// - /// Asserts that the observed by the fails within the next 1 second. - /// - public ExceptionAssertions Throw(string because = "", params object[] becauseArgs) - where TException : Exception - => Throw(TimeSpan.FromSeconds(1), because, becauseArgs); - - /// - public Task> ThrowAsync(string because = "", params object[] becauseArgs) - where TException : Exception - => ThrowAsync(TimeSpan.FromSeconds(1), because, becauseArgs); - - /// - /// Asserts that the observed by the completes within the specified . - /// - public AndConstraint> Complete(TimeSpan timeout, - string because = "", params object[] becauseArgs) - { - var notifications = GetRecordedNotifications(timeout).ExecuteInDefaultSynchronizationContext(); - - return Complete(timeout, because, becauseArgs, notifications); - } - - - /// - public async Task>> CompleteAsync(TimeSpan timeout, - string because = "", params object[] becauseArgs) - { - var notifications = await GetRecordedNotifications(timeout); - - return Complete(timeout, because, becauseArgs, notifications); - } - - /// - /// Asserts that the observed by the completes within the next 1 second. - /// - public AndConstraint> Complete(string because = "", params object[] becauseArgs) - => Complete(TimeSpan.FromSeconds(1), because, becauseArgs); - - /// - public Task>> CompleteAsync(string because = "", params object[] becauseArgs) - => CompleteAsync(TimeSpan.FromSeconds(1), because, becauseArgs); - - /// - /// Asserts that the observed by the does not complete within the specified . - /// - public AndConstraint> NotComplete(TimeSpan timeout, - string because = "", params object[] becauseArgs) - { - bool completed = Observer.RecordedNotificationStream - .Any(recorded => recorded.Value.Kind == NotificationKind.OnCompleted) - .Timeout(timeout) - .Catch(Observable.Return(false)) - .ToTask() - .ExecuteInDefaultSynchronizationContext(); - - Execute.Assertion - .ForCondition(!completed) - .BecauseOf(because, becauseArgs) - .FailWith("Expected observable to not complete{reason}, but it did."); - - return new AndConstraint>(this); - } - - /// - /// Asserts that the observed by the does not complete within the next 100 milliseconds. - /// - public AndConstraint> NotComplete(string because = "", params object[] becauseArgs) - => NotComplete(TimeSpan.FromMilliseconds(100), because, becauseArgs); - - protected Task>>> GetRecordedNotifications(TimeSpan timeout) => - Observer.RecordedNotificationStream - .TakeUntil(recorded => recorded.Value.Kind == NotificationKind.OnError) - .TakeUntil(recorded => recorded.Value.Kind == NotificationKind.OnCompleted) - .Timeout(timeout) - .Catch(Observable.Empty>>()) - .ToList() - .ToTask(); - - protected ExceptionAssertions Throw(IList>> notifications, string because, object[] becauseArgs) - where TException : Exception - { - var exception = notifications - .Where(r => r.Value.Kind == NotificationKind.OnError) - .Select(r => r.Value.Exception) - .FirstOrDefault(); - - TException[] expectedExceptions = extractor.OfType(exception).ToArray(); - - Execute.Assertion - .BecauseOf(because, becauseArgs) - .WithExpectation("Expected observable to throw a <{0}>{reason}, ", typeof(TException)) - .ForCondition(exception != null) - .FailWith("but no exception was thrown.") - .Then - .ForCondition(expectedExceptions.Any()) - .FailWith("but found <{0}>: {1}{2}.", - exception?.GetType(), - Environment.NewLine, - exception) - .Then - .ClearExpectation(); - - return new ExceptionAssertions(expectedExceptions); - } - - protected AndConstraint> Complete(TimeSpan timeout, string because, object[] becauseArgs, IList>> notifications) - { - var exception = notifications - .Where(r => r.Value.Kind == NotificationKind.OnError) - .Select(r => r.Value.Exception) - .FirstOrDefault(); - - Execute.Assertion - .WithExpectation("Expected observable to complete within {0}{reason}, ", timeout) - .BecauseOf(because, becauseArgs) - .ForCondition(exception is null) - .FailWith("but it failed with <{0}>: {1}{2}.", - exception?.GetType(), - Environment.NewLine, - exception) - .Then - .ForCondition(notifications.Any(r => r.Value.Kind == NotificationKind.OnCompleted)) - .FailWith("but it did not."); - - return new AndConstraint>(this); - } - } -} diff --git a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ReactiveExtensions.cs b/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ReactiveExtensions.cs deleted file mode 100644 index 8894ab99..00000000 --- a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/ReactiveExtensions.cs +++ /dev/null @@ -1,108 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Concurrency; -using System.Threading.Tasks; -using FluentAssertions; -using FluentAssertions.Execution; -using Microsoft.Reactive.Testing; - -namespace GraphQL.Client.Tests.Common.FluentAssertions.Reactive -{ - public static class ReactiveExtensions - { - /// - /// Create a new subscribed to this - /// - public static FluentTestObserver Observe(this IObservable observable) => new FluentTestObserver(observable); - - /// - /// Create a new subscribed to this - /// - public static FluentTestObserver Observe(this IObservable observable, IScheduler scheduler) => new FluentTestObserver(observable, scheduler); - - /// - /// Create a new subscribed to this - /// - public static FluentTestObserver Observe(this IObservable observable, TestScheduler scheduler) => new FluentTestObserver(observable, scheduler); - - /// - /// Asserts that the recorded messages contain at lease one item which matches the - /// - public static AndWhichConstraint, IEnumerable> WithMessage( - this AndWhichConstraint, IEnumerable> recorderConstraint, Expression> predicate) - { - if (predicate is null) throw new ArgumentNullException(nameof(predicate)); - - var compiledPredicate = predicate.Compile(); - bool match = recorderConstraint.Subject.Any(compiledPredicate); - - Execute.Assertion - .ForCondition(match) - .FailWith("Expected at least one message from {0} to match {1}, but found none.", recorderConstraint.And.Subject, predicate.Body); - - return recorderConstraint; - } - - /// - /// Asserts that the last recorded message matches the - /// - public static AndWhichConstraint, IEnumerable> WithLastMessage( - this AndWhichConstraint, IEnumerable> recorderConstraint, Expression> predicate) - { - if (predicate is null) - throw new ArgumentNullException(nameof(predicate)); - - bool match = predicate.Compile().Invoke(recorderConstraint.GetLastMessage()); - - Execute.Assertion - .ForCondition(match) - .FailWith("Expected the last message from {0} to match {1}, but it did not.", recorderConstraint.And.Subject, predicate.Body); - - return recorderConstraint; - } - - /// - /// Extracts the last recorded message - /// - public static TPayload GetLastMessage( - this AndWhichConstraint, IEnumerable> - recorderConstraint) => - recorderConstraint.Subject.LastOrDefault(); - - /// - /// Extracts the last recorded message - /// - public static async Task GetLastMessageAsync( - this Task, IEnumerable>> - assertionTask) - { - var constraint = await assertionTask; - return constraint.Subject.LastOrDefault(); - } - - /// - /// Extracts the recorded messages from a number of recorded notifications - /// - public static IEnumerable GetMessages( - this IEnumerable>> recordedNotifications) => recordedNotifications - .Where(r => r.Value.Kind == NotificationKind.OnNext) - .Select(recorded => recorded.Value.Value); - - /// - /// Extracts the last recorded message from a number of recorded notifications - /// - public static TPayload GetLastMessage( - this IEnumerable>> recordedNotifications) => - recordedNotifications.GetMessages().LastOrDefault(); - - /// - /// Clears the recorded notifications on the underlying - /// - public static void Clear( - this AndWhichConstraint, IEnumerable>>> - recorderConstraint) => recorderConstraint.And.Observer.Clear(); - } -} diff --git a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/RollingReplaySubject.cs b/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/RollingReplaySubject.cs deleted file mode 100644 index df779dad..00000000 --- a/tests/GraphQL.Client.Tests.Common/FluentAssertions.Reactive/RollingReplaySubject.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reactive.Linq; -using System.Reactive.Subjects; - -namespace GraphQL.Client.Tests.Common.FluentAssertions.Reactive -{ - /// - /// Clearable taken from James World: https://stackoverflow.com/a/28945444/4340541 - /// - /// - public class RollingReplaySubject : ISubject, IDisposable - { - private readonly ReplaySubject> _subjects; - private readonly IObservable _concatenatedSubjects; - private ISubject _currentSubject; - - public RollingReplaySubject() - { - _subjects = new ReplaySubject>(1); - _concatenatedSubjects = _subjects.Concat(); - _currentSubject = new ReplaySubject(); - _subjects.OnNext(_currentSubject); - } - - public void Clear() - { - _currentSubject.OnCompleted(); - _currentSubject = new ReplaySubject(); - _subjects.OnNext(_currentSubject); - } - - public void OnNext(T value) => _currentSubject.OnNext(value); - - public void OnError(Exception error) => _currentSubject.OnError(error); - - public void OnCompleted() - { - _currentSubject.OnCompleted(); - _subjects.OnCompleted(); - // a quick way to make the current ReplaySubject unreachable - // except to in-flight observers, and not hold up collection - _currentSubject = new Subject(); - } - - public IDisposable Subscribe(IObserver observer) => _concatenatedSubjects.Subscribe(observer); - - public IEnumerable GetSnapshot() - { - var snapshot = new List(); - using (this.Subscribe(item => snapshot.Add(item))) - { - // Deliberately empty; subscribing will add everything to the list. - } - return snapshot; - } - - public void Dispose() - { - OnCompleted(); - _subjects?.Dispose(); - } - } -} diff --git a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj index 24cbf419..01763035 100644 --- a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj +++ b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj @@ -6,11 +6,10 @@ - + - diff --git a/tests/GraphQL.Client.Tests.Common/Helpers/CallbackMonitor.cs b/tests/GraphQL.Client.Tests.Common/Helpers/CallbackMonitor.cs index 6aa7284a..54e51c78 100644 --- a/tests/GraphQL.Client.Tests.Common/Helpers/CallbackMonitor.cs +++ b/tests/GraphQL.Client.Tests.Common/Helpers/CallbackMonitor.cs @@ -46,10 +46,8 @@ public void Reset() public class CallbackAssertions : ReferenceTypeAssertions, CallbackAssertions> { - public CallbackAssertions(CallbackMonitor tester) - { - Subject = tester; - } + public CallbackAssertions(CallbackMonitor tester): base(tester) + { } protected override string Identifier => "callback"; diff --git a/tests/GraphQL.Integration.Tests/.editorconfig b/tests/GraphQL.Integration.Tests/.editorconfig new file mode 100644 index 00000000..d1655ff8 --- /dev/null +++ b/tests/GraphQL.Integration.Tests/.editorconfig @@ -0,0 +1,2 @@ +# Configure await +configure_await_analysis_mode = disabled diff --git a/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj b/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj index 6aca8a5b..37d649e5 100644 --- a/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj +++ b/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj @@ -3,11 +3,11 @@ - netcoreapp3.1 + net5.0 - + diff --git a/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs b/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs index b1f56e62..249ef8e7 100644 --- a/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs +++ b/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs @@ -68,7 +68,7 @@ public async void QueryAsHttpResponseTheory(int id, string name) httpResponse.Errors.Should().BeNull(); httpResponse.Data.Human.Name.Should().Be(name); - httpResponse.StatusCode.Should().BeEquivalentTo(HttpStatusCode.OK); + httpResponse.StatusCode.Should().Be(HttpStatusCode.OK); httpResponse.ResponseHeaders.Date.Should().BeCloseTo(DateTimeOffset.Now, TimeSpan.FromMinutes(1)); } @@ -187,7 +187,7 @@ public async void PreprocessHttpRequestMessageIsCalled() } [Fact] - public void PostRequestCanBeCancelled() + public async Task PostRequestCanBeCancelled() { var graphQLRequest = new GraphQLRequest(@" query Long { @@ -218,7 +218,7 @@ query Long { request.Start(); chatQuery.WaitingOnQueryBlocker.Wait(1000).Should().BeTrue("because the request should have reached the server by then"); cts.Cancel(); - request.Invoking().Should().Throw("because the request was cancelled"); + await request.Invoking().Should().ThrowAsync("because the request was cancelled"); // let the server finish its query chatQuery.LongRunningQueryBlocker.Set(); diff --git a/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs b/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs index 4bedde89..6a53ac26 100644 --- a/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs +++ b/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs @@ -9,12 +9,12 @@ using FluentAssertions; using FluentAssertions.Execution; using FluentAssertions.Extensions; +using FluentAssertions.Reactive; using GraphQL.Client.Abstractions; using GraphQL.Client.Abstractions.Websocket; using GraphQL.Client.Http; using GraphQL.Client.Tests.Common.Chat; using GraphQL.Client.Tests.Common.Chat.Schema; -using GraphQL.Client.Tests.Common.FluentAssertions.Reactive; using GraphQL.Client.Tests.Common.Helpers; using GraphQL.Integration.Tests.Helpers; using Microsoft.Extensions.DependencyInjection; @@ -140,7 +140,7 @@ query Long { request.Start(); chatQuery.WaitingOnQueryBlocker.Wait(1000).Should().BeTrue("because the request should have reached the server by then"); cts.Cancel(); - request.Invoking().Should().Throw("because the request was cancelled"); + await request.Invoking().Should().ThrowAsync("because the request was cancelled"); // let the server finish its query chatQuery.LongRunningQueryBlocker.Set(); diff --git a/tests/GraphQL.Primitives.Tests/.editorconfig b/tests/GraphQL.Primitives.Tests/.editorconfig new file mode 100644 index 00000000..d1655ff8 --- /dev/null +++ b/tests/GraphQL.Primitives.Tests/.editorconfig @@ -0,0 +1,2 @@ +# Configure await +configure_await_analysis_mode = disabled diff --git a/tests/GraphQL.Server.Test/.editorconfig b/tests/GraphQL.Server.Test/.editorconfig new file mode 100644 index 00000000..d1655ff8 --- /dev/null +++ b/tests/GraphQL.Server.Test/.editorconfig @@ -0,0 +1,2 @@ +# Configure await +configure_await_analysis_mode = disabled diff --git a/tests/IntegrationTestServer/.editorconfig b/tests/IntegrationTestServer/.editorconfig new file mode 100644 index 00000000..d1655ff8 --- /dev/null +++ b/tests/IntegrationTestServer/.editorconfig @@ -0,0 +1,2 @@ +# Configure await +configure_await_analysis_mode = disabled diff --git a/tests/tests.props b/tests/tests.props index 19bc6d36..349dec0d 100644 --- a/tests/tests.props +++ b/tests/tests.props @@ -8,14 +8,14 @@ - + + - - + + all runtime;build;native;contentfiles;analyzers;buildtransitive - From cff7f5f9ec5932a823bff0fcc531048dcb314f88 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Mon, 1 Nov 2021 22:46:58 +0100 Subject: [PATCH 353/455] fix test case 'CanReconnectWithSameObservable' --- src/GraphQL.Client/GraphQLHttpClient.cs | 26 +++---------------- .../Websocket/GraphQLHttpWebSocket.cs | 8 +++--- .../Chat/Schema/IChat.cs | 22 +++++++++++----- .../WebsocketTests/Base.cs | 6 ++--- 4 files changed, 26 insertions(+), 36 deletions(-) diff --git a/src/GraphQL.Client/GraphQLHttpClient.cs b/src/GraphQL.Client/GraphQLHttpClient.cs index cd51622c..01e6e40b 100644 --- a/src/GraphQL.Client/GraphQLHttpClient.cs +++ b/src/GraphQL.Client/GraphQLHttpClient.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Net.Http; using System.Net.Http.Headers; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using GraphQL.Client.Abstractions; @@ -97,34 +98,15 @@ public Task> SendMutationAsync(GraphQLRequ /// public IObservable> CreateSubscriptionStream(GraphQLRequest request) - { - if (_disposed) - throw new ObjectDisposedException(nameof(GraphQLHttpClient)); - - var key = new Tuple(request, typeof(TResponse)); - - if (_subscriptionStreams.ContainsKey(key)) - return (IObservable>)_subscriptionStreams[key]; - - var observable = GraphQlHttpWebSocket.CreateSubscriptionStream(request); - - _subscriptionStreams.TryAdd(key, observable); - return observable; - } + => CreateSubscriptionStream(request, null); /// - public IObservable> CreateSubscriptionStream(GraphQLRequest request, Action exceptionHandler) + public IObservable> CreateSubscriptionStream(GraphQLRequest request, Action? exceptionHandler) { if (_disposed) throw new ObjectDisposedException(nameof(GraphQLHttpClient)); - - var key = new Tuple(request, typeof(TResponse)); - - if (_subscriptionStreams.ContainsKey(key)) - return (IObservable>)_subscriptionStreams[key]; - + var observable = GraphQlHttpWebSocket.CreateSubscriptionStream(request, exceptionHandler); - _subscriptionStreams.TryAdd(key, observable); return observable; } diff --git a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs index 0c4ffdff..67a517c6 100644 --- a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs +++ b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs @@ -94,7 +94,7 @@ public GraphQLHttpWebSocket(Uri webSocketUri, GraphQLHttpClient client) /// the to start the subscription /// Optional: exception handler for handling exceptions within the receive pipeline /// a which represents the subscription - public IObservable> CreateSubscriptionStream(GraphQLRequest request, Action exceptionHandler = null) => + public IObservable> CreateSubscriptionStream(GraphQLRequest request, Action? exceptionHandler = null) => Observable.Defer(() => Observable.Create>(async observer => { @@ -169,6 +169,7 @@ public IObservable> CreateSubscriptionStream { + Debug.WriteLine($"disposing subscription {startRequest.Id}, websocket state is '{WebSocketState}'"); // only try to send close request on open websocket if (WebSocketState != WebSocketState.Open) return; @@ -252,10 +253,7 @@ public IObservable> CreateSubscriptionStream>(); } return Observable.Return(t.Item1); - }) - // transform to hot observable and auto-connect - .Publish().RefCount(); - + }); /// /// Send a regular GraphQL request (query, mutation) via websocket /// diff --git a/tests/GraphQL.Client.Tests.Common/Chat/Schema/IChat.cs b/tests/GraphQL.Client.Tests.Common/Chat/Schema/IChat.cs index ba9a97ab..63aaf439 100644 --- a/tests/GraphQL.Client.Tests.Common/Chat/Schema/IChat.cs +++ b/tests/GraphQL.Client.Tests.Common/Chat/Schema/IChat.cs @@ -1,7 +1,10 @@ using System; using System.Collections.Concurrent; +using System.Diagnostics; +using System.Reactive.Disposables; using System.Reactive.Linq; using System.Reactive.Subjects; +using System.Threading; namespace GraphQL.Client.Tests.Common.Chat.Schema { @@ -82,13 +85,20 @@ public MessageFrom Join(string userId) } public IObservable Messages(string user) => - _messageStream - .Select(message => + Observable.Create(observer => + { + Debug.WriteLine($"creating messages stream for user '{user}' on thread {Thread.CurrentThread.ManagedThreadId}"); + return new CompositeDisposable { - message.Sub = user; - return message; - }) - .AsObservable(); + _messageStream.Select(message => + { + message.Sub = user; + return message; + }) + .Subscribe(observer), + Disposable.Create(() => Debug.WriteLine($"disposing messages stream for user '{user}' on thread {Thread.CurrentThread.ManagedThreadId}")) + }; + }); public void AddError(Exception exception) => _messageStream.OnError(exception); diff --git a/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs b/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs index 6a53ac26..8bc9cc7f 100644 --- a/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs +++ b/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs @@ -226,13 +226,13 @@ public async void CanReconnectWithSameObservable() const string message1 = "Hello World"; Debug.WriteLine($"adding message {message1}"); - var response = await ChatClient.AddMessageAsync(message1).ConfigureAwait(true); + var response = await ChatClient.AddMessageAsync(message1); response.Data.AddMessage.Content.Should().Be(message1); await observer.Should().PushAsync(2); observer.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(message1); const string message2 = "How are you?"; - response = await ChatClient.AddMessageAsync(message2).ConfigureAwait(true); + response = await ChatClient.AddMessageAsync(message2); response.Data.AddMessage.Content.Should().Be(message2); await observer.Should().PushAsync(3); observer.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(message2); @@ -247,7 +247,7 @@ public async void CanReconnectWithSameObservable() observer2.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(message2); const string message3 = "lorem ipsum dolor si amet"; - response = await ChatClient.AddMessageAsync(message3).ConfigureAwait(true); + response = await ChatClient.AddMessageAsync(message3); response.Data.AddMessage.Content.Should().Be(message3); await observer2.Should().PushAsync(2); observer2.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(message3); From 4a2a963e98d05a2854aa5d34b443a00a63ca4746 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Mon, 1 Nov 2021 23:04:48 +0100 Subject: [PATCH 354/455] remove subscription cache field --- src/GraphQL.Client/GraphQLHttpClient.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/GraphQL.Client/GraphQLHttpClient.cs b/src/GraphQL.Client/GraphQLHttpClient.cs index 01e6e40b..0d11fe11 100644 --- a/src/GraphQL.Client/GraphQLHttpClient.cs +++ b/src/GraphQL.Client/GraphQLHttpClient.cs @@ -1,11 +1,9 @@ using System; -using System.Collections.Concurrent; using System.Diagnostics; using System.IO; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; -using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using GraphQL.Client.Abstractions; @@ -20,7 +18,6 @@ public class GraphQLHttpClient : IGraphQLClient private GraphQLHttpWebSocket GraphQlHttpWebSocket => _lazyHttpWebSocket.Value; private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); - private readonly ConcurrentDictionary, object> _subscriptionStreams = new ConcurrentDictionary, object>(); private readonly bool _disposeHttpClient = false; From ad5bdb1093a041377c79e54cc693f09f0bc56fd0 Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Mon, 13 Jun 2022 08:44:01 +0300 Subject: [PATCH 355/455] Add dependabot (#392) --- .github/dependabot.yml | 10 ++++++++++ GraphQL.Client.sln | 7 +++++-- 2 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..a4ab6454 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: +- package-ecosystem: "nuget" + directory: "/" + schedule: + interval: "daily" +- package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" diff --git a/GraphQL.Client.sln b/GraphQL.Client.sln index 01a7ad88..2de94cfb 100644 --- a/GraphQL.Client.sln +++ b/GraphQL.Client.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.28407.52 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32228.430 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{47C98B55-08F1-4428-863E-2C5C876DEEFE}" ProjectSection(SolutionItems) = preProject @@ -68,6 +68,9 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphQL.Client.Example", "examples\GraphQL.Client.Example\GraphQL.Client.Example.csproj", "{6B13B87D-1EF4-485F-BC5D-891E2F4DA6CD}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{98D4DDDD-DE15-4997-B888-9BC806C7416C}" + ProjectSection(SolutionItems) = preProject + .github\dependabot.yml = .github\dependabot.yml + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution From 59d53bf641553f7d5dd4d65ce1565d92f5c0d5e4 Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Tue, 12 Jul 2022 01:15:25 +0300 Subject: [PATCH 356/455] Migrate to GraphQL.NET v5 / server v6 --- .github/workflows/branches.yml | 8 ++--- .github/workflows/master.yml | 10 +++---- .../GraphQL.Client.Example.csproj | 2 +- ...aphQL.Client.Abstractions.Websocket.csproj | 4 +-- .../GraphQL.Client.Abstractions.csproj | 8 ----- .../GraphQL.Client.LocalExecution.csproj | 7 ++--- .../GraphQLLocalExecutionClient.cs | 19 +++++------- .../ServiceCollectionExtensions.cs | 2 +- ...raphQL.Client.Serializer.Newtonsoft.csproj | 2 +- ...QL.Client.Serializer.SystemTextJson.csproj | 2 +- src/GraphQL.Client/GraphQL.Client.csproj | 3 +- src/GraphQL.Client/GraphQLHttpClient.cs | 4 +-- .../GraphQL.Primitives.csproj | 2 +- .../GraphQL.Client.Serializer.Tests.csproj | 4 +-- .../Chat/Schema/ChatSubscriptions.cs | 29 +++++++++---------- .../GraphQL.Client.Tests.Common.csproj | 5 ++-- .../StarWars/StarWarsQuery.cs | 9 +++--- .../StarWars/Types/EpisodeEnum.cs | 6 ++-- .../GraphQL.Integration.Tests.csproj | 2 +- .../GraphQL.Primitives.Tests.csproj | 14 ++++----- .../GraphQL.Server.Test.csproj | 9 +++--- tests/GraphQL.Server.Test/Startup.cs | 15 +++++----- .../IntegrationTestServer.csproj | 4 +-- tests/IntegrationTestServer/Startup.cs | 24 +++++++++------ 24 files changed, 95 insertions(+), 99 deletions(-) diff --git a/.github/workflows/branches.yml b/.github/workflows/branches.yml index f1e31304..66468afe 100644 --- a/.github/workflows/branches.yml +++ b/.github/workflows/branches.yml @@ -1,5 +1,5 @@ name: Branch workflow -on: +on: push: branches-ignore: - master @@ -7,10 +7,10 @@ on: - 'releases/**' pull_request: env: - DOTNET_CLI_TELEMETRY_OPTOUT: true + DOTNET_CLI_TELEMETRY_OPTOUT: true MSBUILDSINGLELOADCONTEXT: 1 jobs: - generateVersionInfo: + generateVersionInfo: name: GenerateVersionInfo runs-on: ubuntu-latest steps: @@ -47,7 +47,7 @@ jobs: - name: Build solution run: echo "Current version is \"$GitVersion_SemVer\"" && dotnet build -c Release - name: Create NuGet packages - run: dotnet pack -c Release --no-build -o nupkg + run: dotnet pack -c Release --no-build -o nupkg - name: Upload nuget packages uses: actions/upload-artifact@v1 with: diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 407156d8..daacff76 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -1,11 +1,11 @@ name: Master workflow -on: +on: push: branches: - master - 'release/**' - 'releases/**' - tags: + tags: - v* - V* env: @@ -13,7 +13,7 @@ env: MSBUILDSINGLELOADCONTEXT: 1 jobs: - generateVersionInfo: + generateVersionInfo: name: GenerateVersionInfo runs-on: ubuntu-latest steps: @@ -50,7 +50,7 @@ jobs: - name: Build solution run: echo "Current version is \"$GitVersion_SemVer\"" && dotnet build -c Release - name: Create NuGet packages - run: dotnet pack -c Release --no-build -o nupkg + run: dotnet pack -c Release --no-build -o nupkg - name: Upload nuget packages uses: actions/upload-artifact@v1 with: @@ -85,7 +85,7 @@ jobs: uses: actions/download-artifact@v1 with: name: nupkg - - name: Publish the package to GPR + - name: Publish the package to GPR # using workaround with CURL because of non-functioning upload via dotnet nuget (https://stackoverflow.com/a/58943251) run: | for f in ./nupkg/*.nupkg diff --git a/examples/GraphQL.Client.Example/GraphQL.Client.Example.csproj b/examples/GraphQL.Client.Example/GraphQL.Client.Example.csproj index 8880a7a6..ea44d228 100644 --- a/examples/GraphQL.Client.Example/GraphQL.Client.Example.csproj +++ b/examples/GraphQL.Client.Example/GraphQL.Client.Example.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.1 + net5 false diff --git a/src/GraphQL.Client.Abstractions.Websocket/GraphQL.Client.Abstractions.Websocket.csproj b/src/GraphQL.Client.Abstractions.Websocket/GraphQL.Client.Abstractions.Websocket.csproj index d1980775..048dffbf 100644 --- a/src/GraphQL.Client.Abstractions.Websocket/GraphQL.Client.Abstractions.Websocket.csproj +++ b/src/GraphQL.Client.Abstractions.Websocket/GraphQL.Client.Abstractions.Websocket.csproj @@ -1,10 +1,10 @@ - + Abstractions for the Websocket transport used in GraphQL.Client - netstandard2.0 + netstandard2.0 diff --git a/src/GraphQL.Client.Abstractions/GraphQL.Client.Abstractions.csproj b/src/GraphQL.Client.Abstractions/GraphQL.Client.Abstractions.csproj index 8e1faee1..ef5087f4 100644 --- a/src/GraphQL.Client.Abstractions/GraphQL.Client.Abstractions.csproj +++ b/src/GraphQL.Client.Abstractions/GraphQL.Client.Abstractions.csproj @@ -11,12 +11,4 @@ - - - - - - - - diff --git a/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj b/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj index 00ef36b3..14282946 100644 --- a/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj +++ b/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj @@ -8,10 +8,9 @@ - - - - + + + diff --git a/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs b/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs index 889bc991..ee1edf4d 100644 --- a/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs +++ b/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs @@ -8,8 +8,6 @@ using System.Threading.Tasks; using GraphQL.Client.Abstractions; using GraphQL.Client.Serializer.Newtonsoft; -using GraphQL.NewtonsoftJson; -using GraphQL.Subscription; using GraphQL.Types; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -20,7 +18,7 @@ namespace GraphQL.Client.LocalExecution public static class GraphQLLocalExecutionClient { public static GraphQLLocalExecutionClient New(TSchema schema, IGraphQLJsonSerializer serializer) where TSchema : ISchema - => new GraphQLLocalExecutionClient(schema, serializer, new SubscriptionDocumentExecuter(), new DocumentWriter()); + => new GraphQLLocalExecutionClient(schema, serializer, new DocumentExecuter(), new GraphQL.NewtonsoftJson.GraphQLSerializer()); } public class GraphQLLocalExecutionClient : IGraphQLClient where TSchema : ISchema @@ -41,9 +39,9 @@ public class GraphQLLocalExecutionClient : IGraphQLClient where TSchema public IGraphQLJsonSerializer Serializer { get; } private readonly IDocumentExecuter _documentExecuter; - private readonly IDocumentWriter _documentWriter; + private readonly IGraphQLSerializer _documentSerializer; - public GraphQLLocalExecutionClient(TSchema schema, IGraphQLJsonSerializer serializer, IDocumentExecuter documentExecuter, IDocumentWriter documentWriter) + public GraphQLLocalExecutionClient(TSchema schema, IGraphQLJsonSerializer serializer, IDocumentExecuter documentExecuter, IGraphQLSerializer documentSerializer) { Schema = schema ?? throw new ArgumentNullException(nameof(schema), "no schema configured"); Serializer = serializer ?? throw new ArgumentNullException(nameof(serializer), "please configure the JSON serializer you want to use"); @@ -51,7 +49,7 @@ public GraphQLLocalExecutionClient(TSchema schema, IGraphQLJsonSerializer serial if (!Schema.Initialized) Schema.Initialize(); _documentExecuter = documentExecuter; - _documentWriter = documentWriter; + _documentSerializer = documentSerializer; } public void Dispose() { } @@ -82,7 +80,7 @@ private async Task> ExecuteQueryAsync(Grap private async Task>> ExecuteSubscriptionAsync(GraphQLRequest request, CancellationToken cancellationToken = default) { var result = await ExecuteAsync(request, cancellationToken).ConfigureAwait(false); - var stream = ((SubscriptionExecutionResult)result).Streams?.Values.SingleOrDefault(); + var stream = result.Streams?.Values.SingleOrDefault(); return stream == null ? Observable.Throw>(new InvalidOperationException("the GraphQL execution did not return an observable")) @@ -95,8 +93,7 @@ private async Task ExecuteAsync(GraphQLRequest request, Cancell var deserializedRequest = JsonConvert.DeserializeObject(serializedRequest); var inputs = deserializedRequest.Variables != null - ? (JObject.FromObject(request.Variables, JsonSerializer.Create(_variablesSerializerSettings)) as JObject) - .ToInputs() + ? _documentSerializer.ReadNode(JObject.FromObject(request.Variables, JsonSerializer.Create(_variablesSerializerSettings))) : null; var result = await _documentExecuter.ExecuteAsync(options => @@ -104,7 +101,7 @@ private async Task ExecuteAsync(GraphQLRequest request, Cancell options.Schema = Schema; options.OperationName = deserializedRequest?.OperationName; options.Query = deserializedRequest?.Query; - options.Inputs = inputs; + options.Variables = inputs; options.CancellationToken = cancellationToken; }).ConfigureAwait(false); @@ -114,7 +111,7 @@ private async Task ExecuteAsync(GraphQLRequest request, Cancell private async Task> ExecutionResultToGraphQLResponseAsync(ExecutionResult executionResult, CancellationToken cancellationToken = default) { using var stream = new MemoryStream(); - await _documentWriter.WriteAsync(stream, executionResult, cancellationToken).ConfigureAwait(false); + await _documentSerializer.WriteAsync(stream, executionResult, cancellationToken).ConfigureAwait(false); stream.Seek(0, SeekOrigin.Begin); return await Serializer.DeserializeFromUtf8StreamAsync(stream, cancellationToken).ConfigureAwait(false); } diff --git a/src/GraphQL.Client.LocalExecution/ServiceCollectionExtensions.cs b/src/GraphQL.Client.LocalExecution/ServiceCollectionExtensions.cs index 0fe7b234..ff18f49d 100644 --- a/src/GraphQL.Client.LocalExecution/ServiceCollectionExtensions.cs +++ b/src/GraphQL.Client.LocalExecution/ServiceCollectionExtensions.cs @@ -12,7 +12,7 @@ public static IGraphQLBuilder AddGraphQLLocalExecutionClient(this IServ { services.AddSingleton>(); services.AddSingleton(p => p.GetRequiredService>()); - return services.AddGraphQL(); + return new GraphQLBuilder(services, null); } } } diff --git a/src/GraphQL.Client.Serializer.Newtonsoft/GraphQL.Client.Serializer.Newtonsoft.csproj b/src/GraphQL.Client.Serializer.Newtonsoft/GraphQL.Client.Serializer.Newtonsoft.csproj index 4bbcb784..1ef89e21 100644 --- a/src/GraphQL.Client.Serializer.Newtonsoft/GraphQL.Client.Serializer.Newtonsoft.csproj +++ b/src/GraphQL.Client.Serializer.Newtonsoft/GraphQL.Client.Serializer.Newtonsoft.csproj @@ -1,5 +1,5 @@ - + diff --git a/src/GraphQL.Client.Serializer.SystemTextJson/GraphQL.Client.Serializer.SystemTextJson.csproj b/src/GraphQL.Client.Serializer.SystemTextJson/GraphQL.Client.Serializer.SystemTextJson.csproj index 9433b8dd..ffb763bd 100644 --- a/src/GraphQL.Client.Serializer.SystemTextJson/GraphQL.Client.Serializer.SystemTextJson.csproj +++ b/src/GraphQL.Client.Serializer.SystemTextJson/GraphQL.Client.Serializer.SystemTextJson.csproj @@ -1,5 +1,5 @@ - + diff --git a/src/GraphQL.Client/GraphQL.Client.csproj b/src/GraphQL.Client/GraphQL.Client.csproj index febf2911..4ee05d70 100644 --- a/src/GraphQL.Client/GraphQL.Client.csproj +++ b/src/GraphQL.Client/GraphQL.Client.csproj @@ -17,8 +17,9 @@ + - + diff --git a/src/GraphQL.Client/GraphQLHttpClient.cs b/src/GraphQL.Client/GraphQLHttpClient.cs index 0d11fe11..ea095fc6 100644 --- a/src/GraphQL.Client/GraphQLHttpClient.cs +++ b/src/GraphQL.Client/GraphQLHttpClient.cs @@ -69,7 +69,7 @@ public GraphQLHttpClient(GraphQLHttpClientOptions options, IGraphQLWebsocketJson if (!HttpClient.DefaultRequestHeaders.UserAgent.Any()) HttpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(GetType().Assembly.GetName().Name, GetType().Assembly.GetName().Version.ToString())); - + _lazyHttpWebSocket = new Lazy(CreateGraphQLHttpWebSocket); } @@ -102,7 +102,7 @@ public IObservable> CreateSubscriptionStream(request, exceptionHandler); return observable; } diff --git a/src/GraphQL.Primitives/GraphQL.Primitives.csproj b/src/GraphQL.Primitives/GraphQL.Primitives.csproj index 1c324df3..c4210d9d 100644 --- a/src/GraphQL.Primitives/GraphQL.Primitives.csproj +++ b/src/GraphQL.Primitives/GraphQL.Primitives.csproj @@ -7,5 +7,5 @@ GraphQL netstandard2.0 - + diff --git a/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj b/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj index 3a5af98e..5cf5e783 100644 --- a/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj +++ b/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj @@ -1,9 +1,9 @@ - + - netcoreapp3.1 + net5 diff --git a/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatSubscriptions.cs b/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatSubscriptions.cs index b15ba334..f3fbab63 100644 --- a/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatSubscriptions.cs +++ b/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatSubscriptions.cs @@ -4,7 +4,6 @@ using System.Security.Claims; using GraphQL.Resolvers; using GraphQL.Server.Transports.Subscriptions.Abstractions; -using GraphQL.Subscription; using GraphQL.Types; namespace GraphQL.Client.Tests.Common.Chat.Schema @@ -16,23 +15,23 @@ public class ChatSubscriptions : ObjectGraphType public ChatSubscriptions(IChat chat) { _chat = chat; - AddField(new EventStreamFieldType + AddField(new FieldType { Name = "messageAdded", Type = typeof(MessageType), Resolver = new FuncFieldResolver(ResolveMessage), - Subscriber = new EventStreamResolver(Subscribe) + StreamResolver = new SourceStreamResolver(Subscribe) }); - AddField(new EventStreamFieldType + AddField(new FieldType { Name = "contentAdded", Type = typeof(MessageType), Resolver = new FuncFieldResolver(ResolveMessage), - Subscriber = new EventStreamResolver(Subscribe) + StreamResolver = new SourceStreamResolver(Subscribe) }); - AddField(new EventStreamFieldType + AddField(new FieldType { Name = "messageAddedByUser", Arguments = new QueryArguments( @@ -40,30 +39,30 @@ public ChatSubscriptions(IChat chat) ), Type = typeof(MessageType), Resolver = new FuncFieldResolver(ResolveMessage), - Subscriber = new EventStreamResolver(SubscribeById) + StreamResolver = new SourceStreamResolver(SubscribeById) }); - AddField(new EventStreamFieldType + AddField(new FieldType { Name = "userJoined", Type = typeof(MessageFromType), Resolver = new FuncFieldResolver(context => context.Source as MessageFrom), - Subscriber = new EventStreamResolver(context => _chat.UserJoined()) + StreamResolver = new SourceStreamResolver(context => _chat.UserJoined()) }); - AddField(new EventStreamFieldType + AddField(new FieldType { Name = "failImmediately", Type = typeof(MessageType), Resolver = new FuncFieldResolver(ResolveMessage), - Subscriber = new EventStreamResolver(context => throw new NotSupportedException("this is supposed to fail")) + StreamResolver = new SourceStreamResolver((Func>)(context => throw new NotSupportedException("this is supposed to fail"))) }); } - private IObservable SubscribeById(IResolveEventStreamContext context) + private IObservable SubscribeById(IResolveFieldContext context) { - var messageContext = (MessageHandlingContext) context.UserContext; + var messageContext = (MessageHandlingContext)context.UserContext; var user = messageContext.Get("user"); var sub = "Anonymous"; @@ -83,9 +82,9 @@ private Message ResolveMessage(IResolveFieldContext context) return message; } - private IObservable Subscribe(IResolveEventStreamContext context) + private IObservable Subscribe(IResolveFieldContext context) { - var messageContext = (MessageHandlingContext) context.UserContext; + var messageContext = (MessageHandlingContext)context.UserContext; var user = messageContext.Get("user"); var sub = "Anonymous"; diff --git a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj index 01763035..51ddf3e6 100644 --- a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj +++ b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj @@ -2,13 +2,12 @@ netstandard2.0 - false + false - - + diff --git a/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsQuery.cs b/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsQuery.cs index c416e8c5..ee2b9d3d 100644 --- a/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsQuery.cs +++ b/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsQuery.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using GraphQL.Client.Tests.Common.StarWars.Types; using GraphQL.Types; @@ -10,16 +11,16 @@ public StarWarsQuery(StarWarsData data) { Name = "Query"; - Field("hero", resolve: context => data.GetDroidByIdAsync("3")); - Field( + FieldAsync("hero", resolve: async context => await data.GetDroidByIdAsync("3")); + FieldAsync( "human", arguments: new QueryArguments( new QueryArgument> { Name = "id", Description = "id of the human" } ), - resolve: context => data.GetHumanByIdAsync(context.GetArgument("id")) + resolve: async context => await data.GetHumanByIdAsync(context.GetArgument("id")) ); - Func func = (context, id) => data.GetDroidByIdAsync(id); + Func> func = (context, id) => data.GetDroidByIdAsync(id); FieldDelegate( "droid", diff --git a/tests/GraphQL.Client.Tests.Common/StarWars/Types/EpisodeEnum.cs b/tests/GraphQL.Client.Tests.Common/StarWars/Types/EpisodeEnum.cs index 6823376e..4b9fb577 100644 --- a/tests/GraphQL.Client.Tests.Common/StarWars/Types/EpisodeEnum.cs +++ b/tests/GraphQL.Client.Tests.Common/StarWars/Types/EpisodeEnum.cs @@ -8,9 +8,9 @@ public EpisodeEnum() { Name = "Episode"; Description = "One of the films in the Star Wars Trilogy."; - AddValue("NEWHOPE", "Released in 1977.", 4); - AddValue("EMPIRE", "Released in 1980.", 5); - AddValue("JEDI", "Released in 1983.", 6); + Add("NEWHOPE", 4, "Released in 1977."); + Add("EMPIRE", 5, "Released in 1980."); + Add("JEDI", 6, "Released in 1983."); } } diff --git a/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj b/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj index 37d649e5..9ca8df0b 100644 --- a/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj +++ b/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj @@ -1,7 +1,7 @@ - + net5.0 diff --git a/tests/GraphQL.Primitives.Tests/GraphQL.Primitives.Tests.csproj b/tests/GraphQL.Primitives.Tests/GraphQL.Primitives.Tests.csproj index 99070b4c..5019b79a 100644 --- a/tests/GraphQL.Primitives.Tests/GraphQL.Primitives.Tests.csproj +++ b/tests/GraphQL.Primitives.Tests/GraphQL.Primitives.Tests.csproj @@ -1,13 +1,13 @@ - + - - netcoreapp3.1 - + + net5 + - - - + + + diff --git a/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj b/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj index ad08e233..1b6f7f39 100644 --- a/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj +++ b/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj @@ -1,13 +1,14 @@ - netcoreapp3.1 + net5 - - - + + + + diff --git a/tests/GraphQL.Server.Test/Startup.cs b/tests/GraphQL.Server.Test/Startup.cs index a1f710ee..06eae89b 100644 --- a/tests/GraphQL.Server.Test/Startup.cs +++ b/tests/GraphQL.Server.Test/Startup.cs @@ -1,3 +1,4 @@ +using GraphQL.MicrosoftDI; using GraphQL.Server.Test.GraphQL; using GraphQL.Server.Ui.GraphiQL; using Microsoft.AspNetCore.Builder; @@ -21,17 +22,17 @@ public void Configure(IApplicationBuilder app) app.UseWebSockets(); app.UseGraphQLWebSockets(); app.UseGraphQL(); - app.UseGraphiQLServer(new GraphiQLOptions { }); + app.UseGraphQLGraphiQL(new GraphiQLOptions { }); } public void ConfigureServices(IServiceCollection services) { - services.AddSingleton(); - services.AddGraphQL(options => - { - options.EnableMetrics = true; - options.ExposeExceptions = true; - }).AddWebSockets(); + services.AddGraphQL(builder => builder + .AddSchema() + .AddApolloTracing(enableMetrics: true) + .AddErrorInfoProvider(opt => opt.ExposeExceptionStackTrace = true) + .AddWebSockets() + ); } } } diff --git a/tests/IntegrationTestServer/IntegrationTestServer.csproj b/tests/IntegrationTestServer/IntegrationTestServer.csproj index fab13b6e..884cbaad 100644 --- a/tests/IntegrationTestServer/IntegrationTestServer.csproj +++ b/tests/IntegrationTestServer/IntegrationTestServer.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1 + net5 IntegrationTestServer.Program @@ -10,7 +10,7 @@ - + diff --git a/tests/IntegrationTestServer/Startup.cs b/tests/IntegrationTestServer/Startup.cs index f41856d7..0d1ad68e 100644 --- a/tests/IntegrationTestServer/Startup.cs +++ b/tests/IntegrationTestServer/Startup.cs @@ -2,9 +2,11 @@ using GraphQL.Client.Tests.Common; using GraphQL.Client.Tests.Common.Chat.Schema; using GraphQL.Client.Tests.Common.StarWars; +using GraphQL.MicrosoftDI; using GraphQL.Server; using GraphQL.Server.Ui.Altair; using GraphQL.Server.Ui.GraphiQL; +using GraphQL.SystemTextJson; using GraphQL.Types; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -36,18 +38,22 @@ public void ConfigureServices(IServiceCollection services) // services.AddChatSchema(); services.AddStarWarsSchema(); - services.AddSingleton(); - services.AddGraphQL((options, services) => - { - options.EnableMetrics = true; - var logger = services.GetRequiredService>(); - options.UnhandledExceptionDelegate = ctx => logger.LogError("{Error} occurred", ctx.OriginalException.Message); - }) + services.AddGraphQL(builder => builder + .AddApolloTracing(enableMetrics: true) + .AddHttpMiddleware() + .AddHttpMiddleware() + .AddWebSocketsHttpMiddleware() + .AddWebSocketsHttpMiddleware() + .ConfigureExecutionOptions(opt => opt.UnhandledExceptionDelegate = ctx => + { + var logger = ctx.Context.RequestServices.GetRequiredService>(); + logger.LogError("{Error} occurred", ctx.OriginalException.Message); + return System.Threading.Tasks.Task.CompletedTask; + }) .AddErrorInfoProvider(opt => opt.ExposeExceptionStackTrace = Environment.IsDevelopment()) .AddSystemTextJson() .AddWebSockets() - .AddGraphTypes(typeof(ChatSchema)) - .AddGraphTypes(typeof(StarWarsSchema)); + .AddGraphTypes(typeof(ChatSchema).Assembly)); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. From e014aabea6f46ea5a4d3c58990e12a6b9280042d Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Tue, 12 Jul 2022 12:29:49 +0300 Subject: [PATCH 357/455] Update examples/GraphQL.Client.Example/GraphQL.Client.Example.csproj Co-authored-by: Shane Krueger --- examples/GraphQL.Client.Example/GraphQL.Client.Example.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/GraphQL.Client.Example/GraphQL.Client.Example.csproj b/examples/GraphQL.Client.Example/GraphQL.Client.Example.csproj index ea44d228..046e466d 100644 --- a/examples/GraphQL.Client.Example/GraphQL.Client.Example.csproj +++ b/examples/GraphQL.Client.Example/GraphQL.Client.Example.csproj @@ -2,7 +2,7 @@ Exe - net5 + net6 false From 74276ff76542333ab2706ead3c40a5b9dbe5859a Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Tue, 12 Jul 2022 12:29:59 +0300 Subject: [PATCH 358/455] Update tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj Co-authored-by: Shane Krueger --- .../GraphQL.Client.Serializer.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj b/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj index 5cf5e783..ff238bdd 100644 --- a/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj +++ b/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj @@ -3,7 +3,7 @@ - net5 + net6 From 6e697e52257b6dc99e7b2553c0f119317631969c Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Tue, 12 Jul 2022 12:30:07 +0300 Subject: [PATCH 359/455] Update tests/GraphQL.Primitives.Tests/GraphQL.Primitives.Tests.csproj Co-authored-by: Shane Krueger --- tests/GraphQL.Primitives.Tests/GraphQL.Primitives.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/GraphQL.Primitives.Tests/GraphQL.Primitives.Tests.csproj b/tests/GraphQL.Primitives.Tests/GraphQL.Primitives.Tests.csproj index 5019b79a..ad5efd71 100644 --- a/tests/GraphQL.Primitives.Tests/GraphQL.Primitives.Tests.csproj +++ b/tests/GraphQL.Primitives.Tests/GraphQL.Primitives.Tests.csproj @@ -3,7 +3,7 @@ - net5 + net6 From 9e1c787316aa7ed72f0d84a29d838d1bcc82b801 Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Tue, 12 Jul 2022 12:30:15 +0300 Subject: [PATCH 360/455] Update tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj Co-authored-by: Shane Krueger --- tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj b/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj index 1b6f7f39..bd680444 100644 --- a/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj +++ b/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj @@ -1,7 +1,7 @@ - net5 + net6 From c5620f6ebdcaee84c637b9d960c061d67f1caf76 Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Tue, 12 Jul 2022 12:30:24 +0300 Subject: [PATCH 361/455] Update tests/IntegrationTestServer/IntegrationTestServer.csproj Co-authored-by: Shane Krueger --- tests/IntegrationTestServer/IntegrationTestServer.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/IntegrationTestServer/IntegrationTestServer.csproj b/tests/IntegrationTestServer/IntegrationTestServer.csproj index 884cbaad..1e68549b 100644 --- a/tests/IntegrationTestServer/IntegrationTestServer.csproj +++ b/tests/IntegrationTestServer/IntegrationTestServer.csproj @@ -1,7 +1,7 @@ - net5 + net6 IntegrationTestServer.Program From 16347daae30ee8117059727eaddc5b0c79302e99 Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Tue, 12 Jul 2022 12:33:00 +0300 Subject: [PATCH 362/455] Update src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs Co-authored-by: Shane Krueger --- .../GraphQLLocalExecutionClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs b/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs index ee1edf4d..36705e87 100644 --- a/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs +++ b/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs @@ -39,7 +39,7 @@ public class GraphQLLocalExecutionClient : IGraphQLClient where TSchema public IGraphQLJsonSerializer Serializer { get; } private readonly IDocumentExecuter _documentExecuter; - private readonly IGraphQLSerializer _documentSerializer; + private readonly IGraphQLTextSerializer _documentSerializer; public GraphQLLocalExecutionClient(TSchema schema, IGraphQLJsonSerializer serializer, IDocumentExecuter documentExecuter, IGraphQLSerializer documentSerializer) { From 0bf5b2e797b4a109270b80811227f7127cd374ac Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Tue, 12 Jul 2022 12:33:11 +0300 Subject: [PATCH 363/455] Update src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs Co-authored-by: Shane Krueger --- .../GraphQLLocalExecutionClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs b/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs index 36705e87..0d82a558 100644 --- a/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs +++ b/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs @@ -41,7 +41,7 @@ public class GraphQLLocalExecutionClient : IGraphQLClient where TSchema private readonly IDocumentExecuter _documentExecuter; private readonly IGraphQLTextSerializer _documentSerializer; - public GraphQLLocalExecutionClient(TSchema schema, IGraphQLJsonSerializer serializer, IDocumentExecuter documentExecuter, IGraphQLSerializer documentSerializer) + public GraphQLLocalExecutionClient(TSchema schema, IGraphQLJsonSerializer serializer, IDocumentExecuter documentExecuter, IGraphQLTextSerializer documentSerializer) { Schema = schema ?? throw new ArgumentNullException(nameof(schema), "no schema configured"); Serializer = serializer ?? throw new ArgumentNullException(nameof(serializer), "please configure the JSON serializer you want to use"); From d390e96b4ec146dc9b84e42f4174e050b5edc829 Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Tue, 12 Jul 2022 12:33:50 +0300 Subject: [PATCH 364/455] Update src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs Co-authored-by: Shane Krueger --- .../GraphQLLocalExecutionClient.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs b/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs index 0d82a558..a8ad29c4 100644 --- a/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs +++ b/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs @@ -101,7 +101,8 @@ private async Task ExecuteAsync(GraphQLRequest request, Cancell options.Schema = Schema; options.OperationName = deserializedRequest?.OperationName; options.Query = deserializedRequest?.Query; - options.Variables = inputs; + options.Variables = deserializedRequest?.Variables; + options.Extensions = deserializedRequest?.Extensions; options.CancellationToken = cancellationToken; }).ConfigureAwait(false); From 5ccab4c4290889b46639f5f209fdb27f99f37afe Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Tue, 12 Jul 2022 12:34:25 +0300 Subject: [PATCH 365/455] Update src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs Co-authored-by: Shane Krueger --- .../GraphQLLocalExecutionClient.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs b/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs index a8ad29c4..9ce7546e 100644 --- a/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs +++ b/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs @@ -9,8 +9,6 @@ using GraphQL.Client.Abstractions; using GraphQL.Client.Serializer.Newtonsoft; using GraphQL.Types; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; namespace GraphQL.Client.LocalExecution From 131512097249e0e935d7595ae9d26b4a118433a6 Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Tue, 12 Jul 2022 13:48:03 +0300 Subject: [PATCH 366/455] jump around deps, add GraphQLRequest.Extensions property --- .../GraphQL.Client.LocalExecution.csproj | 1 - .../GraphQLLocalExecutionClient.cs | 43 ++++++------------- ...raphQL.Client.Serializer.Newtonsoft.csproj | 2 +- src/GraphQL.Client/GraphQL.Client.csproj | 2 +- src/GraphQL.Primitives/GraphQLRequest.cs | 15 ++++++- .../BaseSerializeNoCamelCaseTest.cs | 17 +++++--- .../BaseSerializerTest.cs | 36 ++++++++-------- .../GraphQL.Client.Serializer.Tests.csproj | 5 +++ .../NewtonsoftSerializerTest.cs | 5 ++- .../SystemTextJsonSerializerTests.cs | 5 ++- .../TestData/SerializeToBytesTestData.cs | 4 +- .../TestData/SerializeToStringTestData.cs | 8 ++-- .../GraphQL.Client.Tests.Common.csproj | 4 +- .../GraphQL.Integration.Tests.csproj | 4 +- tests/tests.props | 10 ++--- 15 files changed, 83 insertions(+), 78 deletions(-) diff --git a/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj b/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj index 14282946..36d80714 100644 --- a/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj +++ b/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj @@ -9,7 +9,6 @@ - diff --git a/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs b/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs index 9ce7546e..9a741b32 100644 --- a/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs +++ b/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.IO; using System.Linq; using System.Reactive.Linq; @@ -7,31 +6,19 @@ using System.Threading; using System.Threading.Tasks; using GraphQL.Client.Abstractions; -using GraphQL.Client.Serializer.Newtonsoft; using GraphQL.Types; -using Newtonsoft.Json.Serialization; namespace GraphQL.Client.LocalExecution { public static class GraphQLLocalExecutionClient { - public static GraphQLLocalExecutionClient New(TSchema schema, IGraphQLJsonSerializer serializer) where TSchema : ISchema - => new GraphQLLocalExecutionClient(schema, serializer, new DocumentExecuter(), new GraphQL.NewtonsoftJson.GraphQLSerializer()); + public static GraphQLLocalExecutionClient New(TSchema schema, IGraphQLJsonSerializer clientSerializer, IGraphQLTextSerializer serverSerializer) + where TSchema : ISchema + => new GraphQLLocalExecutionClient(schema, new DocumentExecuter(), clientSerializer, serverSerializer); } public class GraphQLLocalExecutionClient : IGraphQLClient where TSchema : ISchema { - private static readonly JsonSerializerSettings _variablesSerializerSettings = new JsonSerializerSettings - { - Formatting = Formatting.Indented, - DateTimeZoneHandling = DateTimeZoneHandling.Local, - ContractResolver = new CamelCasePropertyNamesContractResolver(), - Converters = new List - { - new ConstantCaseEnumConverter() - } - }; - public TSchema Schema { get; } public IGraphQLJsonSerializer Serializer { get; } @@ -39,15 +26,15 @@ public class GraphQLLocalExecutionClient : IGraphQLClient where TSchema private readonly IDocumentExecuter _documentExecuter; private readonly IGraphQLTextSerializer _documentSerializer; - public GraphQLLocalExecutionClient(TSchema schema, IGraphQLJsonSerializer serializer, IDocumentExecuter documentExecuter, IGraphQLTextSerializer documentSerializer) + public GraphQLLocalExecutionClient(TSchema schema, IDocumentExecuter documentExecuter, IGraphQLJsonSerializer serializer, IGraphQLTextSerializer documentSerializer) { Schema = schema ?? throw new ArgumentNullException(nameof(schema), "no schema configured"); + _documentExecuter = documentExecuter; Serializer = serializer ?? throw new ArgumentNullException(nameof(serializer), "please configure the JSON serializer you want to use"); + _documentSerializer = documentSerializer; if (!Schema.Initialized) Schema.Initialize(); - _documentExecuter = documentExecuter; - _documentSerializer = documentSerializer; } public void Dispose() { } @@ -75,6 +62,7 @@ private async Task> ExecuteQueryAsync(Grap var executionResult = await ExecuteAsync(request, cancellationToken).ConfigureAwait(false); return await ExecutionResultToGraphQLResponseAsync(executionResult, cancellationToken).ConfigureAwait(false); } + private async Task>> ExecuteSubscriptionAsync(GraphQLRequest request, CancellationToken cancellationToken = default) { var result = await ExecuteAsync(request, cancellationToken).ConfigureAwait(false); @@ -85,22 +73,17 @@ private async Task>> ExecuteSubscriptionA : stream.SelectMany(executionResult => Observable.FromAsync(token => ExecutionResultToGraphQLResponseAsync(executionResult, token))); } - private async Task ExecuteAsync(GraphQLRequest request, CancellationToken cancellationToken = default) + private async Task ExecuteAsync(GraphQLRequest clientRequest, CancellationToken cancellationToken = default) { - string serializedRequest = Serializer.SerializeToString(request); - - var deserializedRequest = JsonConvert.DeserializeObject(serializedRequest); - var inputs = deserializedRequest.Variables != null - ? _documentSerializer.ReadNode(JObject.FromObject(request.Variables, JsonSerializer.Create(_variablesSerializerSettings))) - : null; + var serverRequest = _documentSerializer.Deserialize(Serializer.SerializeToString(clientRequest)); var result = await _documentExecuter.ExecuteAsync(options => { options.Schema = Schema; - options.OperationName = deserializedRequest?.OperationName; - options.Query = deserializedRequest?.Query; - options.Variables = deserializedRequest?.Variables; - options.Extensions = deserializedRequest?.Extensions; + options.OperationName = serverRequest?.OperationName; + options.Query = serverRequest?.Query; + options.Variables = serverRequest?.Variables; + options.Extensions = serverRequest?.Extensions; options.CancellationToken = cancellationToken; }).ConfigureAwait(false); diff --git a/src/GraphQL.Client.Serializer.Newtonsoft/GraphQL.Client.Serializer.Newtonsoft.csproj b/src/GraphQL.Client.Serializer.Newtonsoft/GraphQL.Client.Serializer.Newtonsoft.csproj index 1ef89e21..27801752 100644 --- a/src/GraphQL.Client.Serializer.Newtonsoft/GraphQL.Client.Serializer.Newtonsoft.csproj +++ b/src/GraphQL.Client.Serializer.Newtonsoft/GraphQL.Client.Serializer.Newtonsoft.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/GraphQL.Client/GraphQL.Client.csproj b/src/GraphQL.Client/GraphQL.Client.csproj index 4ee05d70..8203c2f4 100644 --- a/src/GraphQL.Client/GraphQL.Client.csproj +++ b/src/GraphQL.Client/GraphQL.Client.csproj @@ -16,7 +16,7 @@ - + diff --git a/src/GraphQL.Primitives/GraphQLRequest.cs b/src/GraphQL.Primitives/GraphQLRequest.cs index d2d15a8d..2755122c 100644 --- a/src/GraphQL.Primitives/GraphQLRequest.cs +++ b/src/GraphQL.Primitives/GraphQLRequest.cs @@ -12,6 +12,7 @@ public class GraphQLRequest : Dictionary, IEquatable /// The Query @@ -40,13 +41,23 @@ public object? Variables set => this[VARIABLES_KEY] = value; } + /// + /// Represents the request extensions + /// + public object? Extensions + { + get => TryGetValue(EXTENSIONS_KEY, out object value) ? value : null; + set => this[EXTENSIONS_KEY] = value; + } + public GraphQLRequest() { } - public GraphQLRequest(string query, object? variables = null, string? operationName = null) + public GraphQLRequest(string query, object? variables = null, string? operationName = null, object? extensions = null) { Query = query; Variables = variables; OperationName = operationName; + Extensions = extensions; } public GraphQLRequest(GraphQLRequest other): base(other) { } @@ -84,7 +95,7 @@ public virtual bool Equals(GraphQLRequest? other) /// /// /// - public override int GetHashCode() => (Query, OperationName, Variables).GetHashCode(); + public override int GetHashCode() => (Query, OperationName, Variables, Extensions).GetHashCode(); /// /// Tests whether two specified instances are equivalent diff --git a/tests/GraphQL.Client.Serializer.Tests/BaseSerializeNoCamelCaseTest.cs b/tests/GraphQL.Client.Serializer.Tests/BaseSerializeNoCamelCaseTest.cs index 7ef81660..ce3d51f6 100644 --- a/tests/GraphQL.Client.Serializer.Tests/BaseSerializeNoCamelCaseTest.cs +++ b/tests/GraphQL.Client.Serializer.Tests/BaseSerializeNoCamelCaseTest.cs @@ -13,24 +13,27 @@ namespace GraphQL.Client.Serializer.Tests { public abstract class BaseSerializeNoCamelCaseTest { - public IGraphQLWebsocketJsonSerializer Serializer { get; } + public IGraphQLWebsocketJsonSerializer ClientSerializer { get; } + + public IGraphQLTextSerializer ServerSerializer { get; } public IGraphQLClient ChatClient { get; } public IGraphQLClient StarWarsClient { get; } - protected BaseSerializeNoCamelCaseTest(IGraphQLWebsocketJsonSerializer serializer) + protected BaseSerializeNoCamelCaseTest(IGraphQLWebsocketJsonSerializer clientSerializer, IGraphQLTextSerializer serverSerializer) { - Serializer = serializer; - ChatClient = GraphQLLocalExecutionClient.New(Common.GetChatSchema(), serializer); - StarWarsClient = GraphQLLocalExecutionClient.New(Common.GetStarWarsSchema(), serializer); + ClientSerializer = clientSerializer; + ServerSerializer = serverSerializer; + ChatClient = GraphQLLocalExecutionClient.New(Common.GetChatSchema(), clientSerializer, serverSerializer); + StarWarsClient = GraphQLLocalExecutionClient.New(Common.GetStarWarsSchema(), clientSerializer, serverSerializer); } [Theory] [ClassData(typeof(SerializeToStringTestData))] public void SerializeToStringTest(string expectedJson, GraphQLRequest request) { - var json = Serializer.SerializeToString(request).RemoveWhitespace(); + var json = ClientSerializer.SerializeToString(request).RemoveWhitespace(); json.Should().Be(expectedJson.RemoveWhitespace()); } @@ -38,7 +41,7 @@ public void SerializeToStringTest(string expectedJson, GraphQLRequest request) [ClassData(typeof(SerializeToBytesTestData))] public void SerializeToBytesTest(string expectedJson, GraphQLWebSocketRequest request) { - var json = Encoding.UTF8.GetString(Serializer.SerializeToBytes(request)).RemoveWhitespace(); + var json = Encoding.UTF8.GetString(ClientSerializer.SerializeToBytes(request)).RemoveWhitespace(); json.Should().Be(expectedJson.RemoveWhitespace()); } diff --git a/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs b/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs index 863974ae..8efdccdf 100644 --- a/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs +++ b/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs @@ -15,7 +15,6 @@ using GraphQL.Client.Tests.Common.Chat; using GraphQL.Client.Tests.Common.Chat.Schema; using GraphQL.Client.Tests.Common.Helpers; -using GraphQL.Client.Tests.Common.StarWars; using GraphQL.Client.Tests.Common.StarWars.TestData; using Xunit; @@ -23,24 +22,27 @@ namespace GraphQL.Client.Serializer.Tests { public abstract class BaseSerializerTest { - public IGraphQLWebsocketJsonSerializer Serializer { get; } + public IGraphQLWebsocketJsonSerializer ClientSerializer { get; } + + public IGraphQLTextSerializer ServerSerializer { get; } public IGraphQLClient ChatClient { get; } public IGraphQLClient StarWarsClient { get; } - protected BaseSerializerTest(IGraphQLWebsocketJsonSerializer serializer) + protected BaseSerializerTest(IGraphQLWebsocketJsonSerializer clientSerializer, IGraphQLTextSerializer serverSerializer) { - Serializer = serializer; - ChatClient = GraphQLLocalExecutionClient.New(Common.GetChatSchema(), serializer); - StarWarsClient = GraphQLLocalExecutionClient.New(Common.GetStarWarsSchema(), serializer); + ClientSerializer = clientSerializer; + ServerSerializer = serverSerializer; + ChatClient = GraphQLLocalExecutionClient.New(Common.GetChatSchema(), clientSerializer, serverSerializer); + StarWarsClient = GraphQLLocalExecutionClient.New(Common.GetStarWarsSchema(), clientSerializer, serverSerializer); } [Theory] [ClassData(typeof(SerializeToStringTestData))] public void SerializeToStringTest(string expectedJson, GraphQLRequest request) { - var json = Serializer.SerializeToString(request).RemoveWhitespace(); + var json = ClientSerializer.SerializeToString(request).RemoveWhitespace(); json.Should().BeEquivalentTo(expectedJson.RemoveWhitespace()); } @@ -48,7 +50,7 @@ public void SerializeToStringTest(string expectedJson, GraphQLRequest request) [ClassData(typeof(SerializeToBytesTestData))] public void SerializeToBytesTest(string expectedJson, GraphQLWebSocketRequest request) { - var json = Encoding.UTF8.GetString(Serializer.SerializeToBytes(request)).RemoveWhitespace(); + var json = Encoding.UTF8.GetString(ClientSerializer.SerializeToBytes(request)).RemoveWhitespace(); json.Should().BeEquivalentTo(expectedJson.RemoveWhitespace()); } @@ -96,9 +98,9 @@ public async void DeserializeFromUtf8StreamTest(string json, IGraphQLResponse ex public async Task DeserializeToUnknownType(Type dataType, Stream stream) { - MethodInfo mi = Serializer.GetType().GetMethod("DeserializeFromUtf8StreamAsync", BindingFlags.Instance | BindingFlags.Public); + MethodInfo mi = ClientSerializer.GetType().GetMethod("DeserializeFromUtf8StreamAsync", BindingFlags.Instance | BindingFlags.Public); MethodInfo mi2 = mi.MakeGenericMethod(dataType); - var task = (Task)mi2.Invoke(Serializer, new object[] { stream, CancellationToken.None }); + var task = (Task)mi2.Invoke(ClientSerializer, new object[] { stream, CancellationToken.None }); await task; var resultProperty = task.GetType().GetProperty("Result", BindingFlags.Public | BindingFlags.Instance); var result = resultProperty.GetValue(task); @@ -108,9 +110,9 @@ public async Task DeserializeToUnknownType(Type dataType, Stre [Fact] public async void CanDeserializeExtensions() { - var response = await ChatClient.SendQueryAsync(new GraphQLRequest("query { extensionsTest }"), - () => new { extensionsTest = "" }) - ; + var response = await ChatClient.SendQueryAsync( + new GraphQLRequest("query { extensionsTest }"), + () => new { extensionsTest = "" }); response.Errors.Should().NotBeNull(); response.Errors.Should().ContainSingle(); @@ -136,8 +138,8 @@ query Droid($id: String!) { name } }", - new { id = id.ToString() }, - "Human"); + new { id = id.ToString() }, + "Human"); var response = await StarWarsClient.SendQueryAsync(graphQLRequest, () => new { Human = new { Name = string.Empty } }); @@ -162,7 +164,7 @@ public class WithNullable [Fact] public void CanSerializeNullableInt() { - Action action = () => Serializer.SerializeToString(new GraphQLRequest + Action action = () => ClientSerializer.SerializeToString(new GraphQLRequest { Query = "{}", Variables = new WithNullable @@ -182,7 +184,7 @@ public class WithNullableStruct [Fact] public void CanSerializeNullableStruct() { - Action action = () => Serializer.SerializeToString(new GraphQLRequest + Action action = () => ClientSerializer.SerializeToString(new GraphQLRequest { Query = "{}", Variables = new WithNullableStruct diff --git a/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj b/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj index ff238bdd..49a25c09 100644 --- a/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj +++ b/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj @@ -6,6 +6,11 @@ net6 + + + + + diff --git a/tests/GraphQL.Client.Serializer.Tests/NewtonsoftSerializerTest.cs b/tests/GraphQL.Client.Serializer.Tests/NewtonsoftSerializerTest.cs index 43ade957..481a23c0 100644 --- a/tests/GraphQL.Client.Serializer.Tests/NewtonsoftSerializerTest.cs +++ b/tests/GraphQL.Client.Serializer.Tests/NewtonsoftSerializerTest.cs @@ -5,12 +5,13 @@ namespace GraphQL.Client.Serializer.Tests { public class NewtonsoftSerializerTest : BaseSerializerTest { - public NewtonsoftSerializerTest() : base(new NewtonsoftJsonSerializer()) { } + public NewtonsoftSerializerTest() + : base(new NewtonsoftJsonSerializer(), new NewtonsoftJson.GraphQLSerializer()) { } } public class NewtonsoftSerializeNoCamelCaseTest : BaseSerializeNoCamelCaseTest { public NewtonsoftSerializeNoCamelCaseTest() - : base(new NewtonsoftJsonSerializer(new JsonSerializerSettings(){ Converters = { new ConstantCaseEnumConverter() } })) { } + : base(new NewtonsoftJsonSerializer(new JsonSerializerSettings { Converters = { new ConstantCaseEnumConverter() } }), new NewtonsoftJson.GraphQLSerializer()) { } } } diff --git a/tests/GraphQL.Client.Serializer.Tests/SystemTextJsonSerializerTests.cs b/tests/GraphQL.Client.Serializer.Tests/SystemTextJsonSerializerTests.cs index 153bcd4d..d5e04733 100644 --- a/tests/GraphQL.Client.Serializer.Tests/SystemTextJsonSerializerTests.cs +++ b/tests/GraphQL.Client.Serializer.Tests/SystemTextJsonSerializerTests.cs @@ -6,12 +6,13 @@ namespace GraphQL.Client.Serializer.Tests { public class SystemTextJsonSerializerTests : BaseSerializerTest { - public SystemTextJsonSerializerTests() : base(new SystemTextJsonSerializer()) { } + public SystemTextJsonSerializerTests() + : base(new SystemTextJsonSerializer(), new GraphQL.SystemTextJson.GraphQLSerializer()) { } } public class SystemTextJsonSerializeNoCamelCaseTest : BaseSerializeNoCamelCaseTest { public SystemTextJsonSerializeNoCamelCaseTest() - : base(new SystemTextJsonSerializer(new JsonSerializerOptions(){Converters = { new JsonStringEnumConverter(new ConstantCaseJsonNamingPolicy(), false)}}.SetupImmutableConverter())) { } + : base(new SystemTextJsonSerializer(new JsonSerializerOptions { Converters = { new JsonStringEnumConverter(new ConstantCaseJsonNamingPolicy(), false)}}.SetupImmutableConverter()), new GraphQL.SystemTextJson.GraphQLSerializer()) { } } } diff --git a/tests/GraphQL.Client.Serializer.Tests/TestData/SerializeToBytesTestData.cs b/tests/GraphQL.Client.Serializer.Tests/TestData/SerializeToBytesTestData.cs index de76f5d8..3b581cc5 100644 --- a/tests/GraphQL.Client.Serializer.Tests/TestData/SerializeToBytesTestData.cs +++ b/tests/GraphQL.Client.Serializer.Tests/TestData/SerializeToBytesTestData.cs @@ -9,7 +9,7 @@ public class SerializeToBytesTestData : IEnumerable public IEnumerator GetEnumerator() { yield return new object[] { - "{\"id\":\"1234567\",\"type\":\"start\",\"payload\":{\"query\":\"simplequerystring\",\"variables\":null,\"operationName\":null}}", + "{\"id\":\"1234567\",\"type\":\"start\",\"payload\":{\"query\":\"simplequerystring\",\"variables\":null,\"operationName\":null,\"extensions\":null}}", new GraphQLWebSocketRequest { Id = "1234567", Type = GraphQLWebSocketMessageType.GQL_START, @@ -17,7 +17,7 @@ public IEnumerator GetEnumerator() } }; yield return new object[] { - "{\"id\":\"34476567\",\"type\":\"start\",\"payload\":{\"query\":\"simplequerystring\",\"variables\":{\"camelCaseProperty\":\"camelCase\",\"PascalCaseProperty\":\"PascalCase\"},\"operationName\":null}}", + "{\"id\":\"34476567\",\"type\":\"start\",\"payload\":{\"query\":\"simplequerystring\",\"variables\":{\"camelCaseProperty\":\"camelCase\",\"PascalCaseProperty\":\"PascalCase\"},\"operationName\":null,\"extensions\":null}}", new GraphQLWebSocketRequest { Id = "34476567", Type = GraphQLWebSocketMessageType.GQL_START, diff --git a/tests/GraphQL.Client.Serializer.Tests/TestData/SerializeToStringTestData.cs b/tests/GraphQL.Client.Serializer.Tests/TestData/SerializeToStringTestData.cs index e5285681..87634dad 100644 --- a/tests/GraphQL.Client.Serializer.Tests/TestData/SerializeToStringTestData.cs +++ b/tests/GraphQL.Client.Serializer.Tests/TestData/SerializeToStringTestData.cs @@ -10,19 +10,19 @@ public class SerializeToStringTestData : IEnumerable public IEnumerator GetEnumerator() { yield return new object[] { - "{\"query\":\"simplequerystring\",\"variables\":null,\"operationName\":null}", + "{\"query\":\"simplequerystring\",\"variables\":null,\"operationName\":null,\"extensions\":null}", new GraphQLRequest("simple query string") }; yield return new object[] { - "{\"query\":\"simplequerystring\",\"variables\":{\"camelCaseProperty\":\"camelCase\",\"PascalCaseProperty\":\"PascalCase\"},\"operationName\":null}", + "{\"query\":\"simplequerystring\",\"variables\":{\"camelCaseProperty\":\"camelCase\",\"PascalCaseProperty\":\"PascalCase\"},\"operationName\":null,\"extensions\":null}", new GraphQLRequest("simple query string", new { camelCaseProperty = "camelCase", PascalCaseProperty = "PascalCase"}) }; yield return new object[] { - "{\"query\":\"simplequerystring\",\"variables\":null,\"operationName\":null,\"authentication\":\"an-authentication-token\"}", + "{\"query\":\"simplequerystring\",\"variables\":null,\"operationName\":null,\"extensions\":null,\"authentication\":\"an-authentication-token\"}", new GraphQLRequest("simple query string"){{"authentication", "an-authentication-token"}} }; yield return new object[] { - "{\"query\":\"enumtest\",\"variables\":{\"enums\":[\"REGULAR\",\"PASCAL_CASE\",\"CAMEL_CASE\",\"LOWER\",\"UPPER\",\"CONSTANT_CASE\"]},\"operationName\":null}", + "{\"query\":\"enumtest\",\"variables\":{\"enums\":[\"REGULAR\",\"PASCAL_CASE\",\"CAMEL_CASE\",\"LOWER\",\"UPPER\",\"CONSTANT_CASE\"]},\"operationName\":null,\"extensions\":null}", new GraphQLRequest("enumtest", new { enums = Enum.GetValues(typeof(TestEnum)).Cast()}) }; } diff --git a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj index 51ddf3e6..1b285443 100644 --- a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj +++ b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj @@ -6,9 +6,9 @@ - + - + diff --git a/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj b/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj index 9ca8df0b..e854727b 100644 --- a/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj +++ b/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj @@ -3,11 +3,11 @@ - net5.0 + net6 - + diff --git a/tests/tests.props b/tests/tests.props index 349dec0d..7d4a0255 100644 --- a/tests/tests.props +++ b/tests/tests.props @@ -3,16 +3,16 @@ - false + false $(NoWarn);NU1701;IDE1006 - - + + - - + + all runtime;build;native;contentfiles;analyzers;buildtransitive From 923495123ee852691271d7a1d9bae734abcee844 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Jun 2022 05:44:22 +0000 Subject: [PATCH 367/455] Bump actions/checkout from 2 to 3 Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/branches.yml | 6 +++--- .github/workflows/master.yml | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/branches.yml b/.github/workflows/branches.yml index 66468afe..84137705 100644 --- a/.github/workflows/branches.yml +++ b/.github/workflows/branches.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 - name: Restore dotnet tools @@ -36,7 +36,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Download version info file uses: actions/download-artifact@v1 with: @@ -60,7 +60,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Download version info file uses: actions/download-artifact@v1 with: diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index daacff76..6f6da04d 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 - name: Restore dotnet tools @@ -39,7 +39,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Download version info file uses: actions/download-artifact@v1 with: @@ -63,7 +63,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Download version info file uses: actions/download-artifact@v1 with: @@ -80,7 +80,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Download nuget packages uses: actions/download-artifact@v1 with: From ce341169a7aabb231a63daa656bbd2b373f6980f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Jul 2022 12:35:41 +0000 Subject: [PATCH 368/455] Bump actions/download-artifact from 1 to 3 Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 1 to 3. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v1...v3) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/branches.yml | 4 ++-- .github/workflows/master.yml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/branches.yml b/.github/workflows/branches.yml index 84137705..9b0ee728 100644 --- a/.github/workflows/branches.yml +++ b/.github/workflows/branches.yml @@ -38,7 +38,7 @@ jobs: - name: Checkout uses: actions/checkout@v3 - name: Download version info file - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v3 with: name: gitversion path: ./ @@ -62,7 +62,7 @@ jobs: - name: Checkout uses: actions/checkout@v3 - name: Download version info file - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v3 with: name: gitversion path: ./ diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 6f6da04d..9b99aa36 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -41,7 +41,7 @@ jobs: - name: Checkout uses: actions/checkout@v3 - name: Download version info file - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v3 with: name: gitversion path: ./ @@ -65,7 +65,7 @@ jobs: - name: Checkout uses: actions/checkout@v3 - name: Download version info file - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v3 with: name: gitversion path: ./ @@ -82,7 +82,7 @@ jobs: - name: Checkout uses: actions/checkout@v3 - name: Download nuget packages - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v3 with: name: nupkg - name: Publish the package to GPR From 51ae3f1a831e2759445cdaab410a5e06ab2ddaff Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Tue, 12 Jul 2022 14:38:14 +0200 Subject: [PATCH 369/455] Fix path for nupkg checkout --- .github/workflows/master.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 9b99aa36..3c066c3d 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -85,6 +85,7 @@ jobs: uses: actions/download-artifact@v3 with: name: nupkg + path: nupkg - name: Publish the package to GPR # using workaround with CURL because of non-functioning upload via dotnet nuget (https://stackoverflow.com/a/58943251) run: | From cb62813f7763dfca9ce13963c4cd94d15a8b303e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Jul 2022 12:23:04 +0000 Subject: [PATCH 370/455] Bump actions/upload-artifact from 1 to 3 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 1 to 3. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v1...v3) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/branches.yml | 4 ++-- .github/workflows/master.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/branches.yml b/.github/workflows/branches.yml index 9b0ee728..e493fc97 100644 --- a/.github/workflows/branches.yml +++ b/.github/workflows/branches.yml @@ -25,7 +25,7 @@ jobs: - name: Generate version info from git history run: dotnet gitversion /output json | jq -r 'to_entries|map("GitVersion_\(.key)=\(.value|tostring)")|.[]' >> gitversion.env && cat gitversion.env - name: Upload version info file - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v3 with: name: gitversion path: gitversion.env @@ -49,7 +49,7 @@ jobs: - name: Create NuGet packages run: dotnet pack -c Release --no-build -o nupkg - name: Upload nuget packages - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v3 with: name: nupkg path: nupkg diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 3c066c3d..3fbd5662 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -28,7 +28,7 @@ jobs: - name: Generate version info from git history run: dotnet gitversion /output json | jq -r 'to_entries|map("GitVersion_\(.key)=\(.value|tostring)")|.[]' >> gitversion.env && cat gitversion.env - name: Upload version info file - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v3 with: name: gitversion path: gitversion.env @@ -52,7 +52,7 @@ jobs: - name: Create NuGet packages run: dotnet pack -c Release --no-build -o nupkg - name: Upload nuget packages - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v3 with: name: nupkg path: nupkg From ebe5231d99e1cf1da84e911cd593ca557366fb2a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Jul 2022 12:35:44 +0000 Subject: [PATCH 371/455] Bump xunit.runner.visualstudio from 2.4.1 to 2.4.5 Bumps [xunit.runner.visualstudio](https://github.com/xunit/visualstudio.xunit) from 2.4.1 to 2.4.5. - [Release notes](https://github.com/xunit/visualstudio.xunit/releases) - [Commits](https://github.com/xunit/visualstudio.xunit/commits) --- updated-dependencies: - dependency-name: xunit.runner.visualstudio dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- tests/tests.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tests.props b/tests/tests.props index 7d4a0255..f2157f5f 100644 --- a/tests/tests.props +++ b/tests/tests.props @@ -11,7 +11,7 @@ - + all runtime;build;native;contentfiles;analyzers;buildtransitive From 3ae63ec89efba0df6561fc4cdaa5f68bcc6d6fbd Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Tue, 12 Jul 2022 15:13:33 +0200 Subject: [PATCH 372/455] reduce branches workflow to single job --- .github/workflows/branches.yml | 56 +++++++++------------------------- 1 file changed, 14 insertions(+), 42 deletions(-) diff --git a/.github/workflows/branches.yml b/.github/workflows/branches.yml index e493fc97..9160192b 100644 --- a/.github/workflows/branches.yml +++ b/.github/workflows/branches.yml @@ -6,12 +6,15 @@ on: - 'release/**' - 'releases/**' pull_request: + env: + DOTNET_NOLOGO: true DOTNET_CLI_TELEMETRY_OPTOUT: true MSBUILDSINGLELOADCONTEXT: 1 + jobs: - generateVersionInfo: - name: GenerateVersionInfo + build: + name: Build and Test runs-on: ubuntu-latest steps: - name: Checkout @@ -23,50 +26,19 @@ jobs: - name: Fetch complete repository including tags run: git fetch --tags --force --prune && git describe - name: Generate version info from git history - run: dotnet gitversion /output json | jq -r 'to_entries|map("GitVersion_\(.key)=\(.value|tostring)")|.[]' >> gitversion.env && cat gitversion.env - - name: Upload version info file - uses: actions/upload-artifact@v3 - with: - name: gitversion - path: gitversion.env - - build: - name: Build - needs: generateVersionInfo - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Download version info file - uses: actions/download-artifact@v3 - with: - name: gitversion - path: ./ - - name: Inject version info into environment - run: cat ./gitversion.env >> $GITHUB_ENV + run: dotnet gitversion /output json | jq -r 'to_entries|map("GitVersion_\(.key)=\(.value|tostring)")|.[]' >> $GITHUB_ENV + - name: Print current version + run: echo "Current version is \"$GitVersion_SemVer\"" + - name: Install dependencies + run: dotnet restore - name: Build solution - run: echo "Current version is \"$GitVersion_SemVer\"" && dotnet build -c Release + run: dotnet build --no-restore -c Release + - name: Run Tests + run: dotnet test -c Release --no-restore --no-build - name: Create NuGet packages - run: dotnet pack -c Release --no-build -o nupkg + run: dotnet pack -c Release --no-restore --no-build -o nupkg - name: Upload nuget packages uses: actions/upload-artifact@v3 with: name: nupkg path: nupkg - - test: - name: Test - needs: [build, generateVersionInfo] - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Download version info file - uses: actions/download-artifact@v3 - with: - name: gitversion - path: ./ - - name: Inject version info into environment - run: cat ./gitversion.env >> $GITHUB_ENV - - name: Run tests - run: echo "Current version is \"$GitVersion_SemVer\"" && dotnet test -c Release From 1a1dc9eaf0266be1124b1de42af3b8c5b552cb1b Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Tue, 12 Jul 2022 15:27:43 +0200 Subject: [PATCH 373/455] add setup-dotnet --- .github/workflows/branches.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/branches.yml b/.github/workflows/branches.yml index 9160192b..dc383883 100644 --- a/.github/workflows/branches.yml +++ b/.github/workflows/branches.yml @@ -21,6 +21,10 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 0 + - name: Setup .NET Core SDK + uses: actions/setup-dotnet@v2 + with: + dotnet-version: "6.0.x" - name: Restore dotnet tools run: dotnet tool restore - name: Fetch complete repository including tags From 8e60104f477cb39caf1ff05169ee9a382119093a Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Tue, 12 Jul 2022 15:32:24 +0200 Subject: [PATCH 374/455] add older dotnet-sdks --- .github/workflows/branches.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/branches.yml b/.github/workflows/branches.yml index dc383883..764bf9db 100644 --- a/.github/workflows/branches.yml +++ b/.github/workflows/branches.yml @@ -24,7 +24,10 @@ jobs: - name: Setup .NET Core SDK uses: actions/setup-dotnet@v2 with: - dotnet-version: "6.0.x" + dotnet-version: | + 3.1.x + 5.0.x + 6.0.x - name: Restore dotnet tools run: dotnet tool restore - name: Fetch complete repository including tags From 4b06fb8bc7d28e8b6d0d5360f6dbe6365ed9ca28 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Tue, 12 Jul 2022 15:45:57 +0200 Subject: [PATCH 375/455] check if version has been resolved --- .github/workflows/branches.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/branches.yml b/.github/workflows/branches.yml index 764bf9db..40ad9123 100644 --- a/.github/workflows/branches.yml +++ b/.github/workflows/branches.yml @@ -25,8 +25,6 @@ jobs: uses: actions/setup-dotnet@v2 with: dotnet-version: | - 3.1.x - 5.0.x 6.0.x - name: Restore dotnet tools run: dotnet tool restore @@ -34,6 +32,11 @@ jobs: run: git fetch --tags --force --prune && git describe - name: Generate version info from git history run: dotnet gitversion /output json | jq -r 'to_entries|map("GitVersion_\(.key)=\(.value|tostring)")|.[]' >> $GITHUB_ENV + - name: Check that the version number has been resolved + if: ${{ !env.GitVersion_SemVer }} + run: | + echo Error! Version number not resolved! + exit 1 - name: Print current version run: echo "Current version is \"$GitVersion_SemVer\"" - name: Install dependencies From 6b0236aa4035a44e7f4a435b474e11d65cf1664c Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Tue, 12 Jul 2022 15:48:06 +0200 Subject: [PATCH 376/455] upgrade gitversion.tool --- dotnet-tools.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet-tools.json b/dotnet-tools.json index 3c532709..7254cc09 100644 --- a/dotnet-tools.json +++ b/dotnet-tools.json @@ -8,7 +8,7 @@ ] }, "gitversion.tool": { - "version": "5.6.7", + "version": "5.10.3", "commands": [ "dotnet-gitversion" ] From 0022e39e275c02a572f7ad8ca1bf34e8867194e0 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Tue, 12 Jul 2022 15:56:24 +0200 Subject: [PATCH 377/455] update master workflow --- .github/workflows/branches.yml | 2 +- .github/workflows/master.yml | 94 ++++++++++------------------------ 2 files changed, 29 insertions(+), 67 deletions(-) diff --git a/.github/workflows/branches.yml b/.github/workflows/branches.yml index 40ad9123..3807f0a0 100644 --- a/.github/workflows/branches.yml +++ b/.github/workflows/branches.yml @@ -32,7 +32,7 @@ jobs: run: git fetch --tags --force --prune && git describe - name: Generate version info from git history run: dotnet gitversion /output json | jq -r 'to_entries|map("GitVersion_\(.key)=\(.value|tostring)")|.[]' >> $GITHUB_ENV - - name: Check that the version number has been resolved + - name: Fail if the version number has not been resolved if: ${{ !env.GitVersion_SemVer }} run: | echo Error! Version number not resolved! diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 3fbd5662..31cc1f10 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -5,91 +5,53 @@ on: - master - 'release/**' - 'releases/**' - tags: - - v* - - V* + env: + DOTNET_NOLOGO: true DOTNET_CLI_TELEMETRY_OPTOUT: true MSBUILDSINGLELOADCONTEXT: 1 jobs: - generateVersionInfo: - name: GenerateVersionInfo + build: + name: Build and Test runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 with: fetch-depth: 0 + - name: Setup .NET Core SDK + uses: actions/setup-dotnet@v2 + with: + dotnet-version: "6.0.x" + source-url: https://nuget.pkg.github.com/graphql-dotnet/index.json + env: + NUGET_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} - name: Restore dotnet tools run: dotnet tool restore - name: Fetch complete repository including tags run: git fetch --tags --force --prune && git describe - name: Generate version info from git history - run: dotnet gitversion /output json | jq -r 'to_entries|map("GitVersion_\(.key)=\(.value|tostring)")|.[]' >> gitversion.env && cat gitversion.env - - name: Upload version info file - uses: actions/upload-artifact@v3 - with: - name: gitversion - path: gitversion.env - - build: - name: Build - needs: generateVersionInfo - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Download version info file - uses: actions/download-artifact@v3 - with: - name: gitversion - path: ./ - - name: Inject version info into environment - run: cat ./gitversion.env >> $GITHUB_ENV + run: dotnet gitversion /output json | jq -r 'to_entries|map("GitVersion_\(.key)=\(.value|tostring)")|.[]' >> $GITHUB_ENV + - name: Fail if the version number has not been resolved + if: ${{ !env.GitVersion_SemVer }} + run: | + echo Error! Version number not resolved! + exit 1 + - name: Print current version + run: echo "Current version is \"$GitVersion_SemVer\"" + - name: Install dependencies + run: dotnet restore - name: Build solution - run: echo "Current version is \"$GitVersion_SemVer\"" && dotnet build -c Release + run: dotnet build --no-restore -c Release + - name: Run Tests + run: dotnet test -c Release --no-restore --no-build - name: Create NuGet packages - run: dotnet pack -c Release --no-build -o nupkg - - name: Upload nuget packages + run: dotnet pack -c Release --no-restore --no-build -o nupkg + - name: Upload nuget packages as artifacts uses: actions/upload-artifact@v3 with: name: nupkg path: nupkg - - test: - name: Test - needs: [build, generateVersionInfo] - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Download version info file - uses: actions/download-artifact@v3 - with: - name: gitversion - path: ./ - - name: Inject version info into environment - run: cat ./gitversion.env >> $GITHUB_ENV - - name: Run tests - run: echo "Current version is \"$GitVersion_SemVer\"" && dotnet test -c Release - - publish: - name: Publish - needs: [test] - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Download nuget packages - uses: actions/download-artifact@v3 - with: - name: nupkg - path: nupkg - - name: Publish the package to GPR - # using workaround with CURL because of non-functioning upload via dotnet nuget (https://stackoverflow.com/a/58943251) - run: | - for f in ./nupkg/*.nupkg - do - curl -vX PUT -u "graphql-dotnet:${{secrets.GITHUB_TOKEN}}" -F package=@$f https://nuget.pkg.github.com/graphql-dotnet/ - done + - name: Publish Nuget packages to GitHub registry + run: dotnet nuget push "nupkg/*" -k ${{secrets.GITHUB_TOKEN}} From 5f666ae6df8a8ce3dc780ab539e622655d0f94c1 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Tue, 12 Jul 2022 16:05:43 +0200 Subject: [PATCH 378/455] add publish workflow --- .github/workflows/publish.yml | 59 +++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 00000000..d890925f --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,59 @@ +name: Publish +on: + release: + types: + - published + +env: + DOTNET_NOLOGO: true + DOTNET_CLI_TELEMETRY_OPTOUT: true + MSBUILDSINGLELOADCONTEXT: 1 + +jobs: + build: + name: Build and Test + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Check github.ref starts with 'refs/tags/' + if: ${{ !startsWith(github.ref, 'refs/tags/') }} + run: | + echo Error! github.ref does not start with 'refs/tags' + echo github.ref: ${{ github.ref }} + exit 1 + - name: Setup .NET Core SDK + uses: actions/setup-dotnet@v2 + with: + dotnet-version: "6.0.x" + source-url: https://api.nuget.org/v3/index.json + env: + NUGET_AUTH_TOKEN: ${{secrets.NUGET_API_KEY}} + - name: Restore dotnet tools + run: dotnet tool restore + - name: Fetch complete repository including tags + run: git fetch --tags --force --prune && git describe + - name: Generate version info from git history + run: dotnet gitversion /output json | jq -r 'to_entries|map("GitVersion_\(.key)=\(.value|tostring)")|.[]' >> $GITHUB_ENV + - name: Fail if the version number has not been resolved + if: ${{ !env.GitVersion_SemVer }} + run: | + echo Error! Version number not resolved! + exit 1 + - name: Print current version + run: echo "Current version is \"$GitVersion_SemVer\"" + - name: Install dependencies + run: dotnet restore + - name: Build solution + run: dotnet build --no-restore -c Release + - name: Create NuGet packages + run: dotnet pack -c Release --no-restore --no-build -o nupkg + - name: Upload nuget packages as artifacts + uses: actions/upload-artifact@v3 + with: + name: nupkg + path: nupkg + - name: Publish Nuget packages to GitHub registry + run: dotnet nuget push "nupkg/*" -k ${{secrets.NUGET_API_KEY}} \ No newline at end of file From 54f5e8ac8172262b9e0106418f91de176346dd11 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Tue, 12 Jul 2022 16:34:58 +0200 Subject: [PATCH 379/455] add script to upload nuget packages as release artifacts --- .github/workflows/publish.yml | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index d890925f..e070df28 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -55,5 +55,23 @@ jobs: with: name: nupkg path: nupkg - - name: Publish Nuget packages to GitHub registry - run: dotnet nuget push "nupkg/*" -k ${{secrets.NUGET_API_KEY}} \ No newline at end of file + - name: Publish Nuget packages to Nuget registry + run: dotnet nuget push "nupkg/*" -k ${{secrets.NUGET_API_KEY}} + - name: Upload Nuget packages as release artifacts + uses: actions/github-script@v6 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + console.log('environment', process.versions); + const fs = require('fs').promises; + const { repo: { owner, repo }, sha } = context; + for (let file of await fs.readdir('nupkg')) { + console.log('uploading', file); + await github.rest.repos.uploadReleaseAsset({ + owner, + repo, + release_id: ${{ github.event.release.id }}, + name: file, + data: await fs.readFile(`nupkg/${file}`) + }); + } \ No newline at end of file From 7f49e38f82ba146dda2548bdb49209484186291c Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Tue, 12 Jul 2022 16:36:18 +0200 Subject: [PATCH 380/455] add final newiline --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index e070df28..ddff922e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -74,4 +74,4 @@ jobs: name: file, data: await fs.readFile(`nupkg/${file}`) }); - } \ No newline at end of file + } From 3304558c8dfc396af872a77ec95ad8c2843ba469 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Jul 2022 00:28:12 +0300 Subject: [PATCH 381/455] Bump Microsoft.AspNetCore.Mvc.Testing from 6.0.6 to 6.0.7 (#413) Bumps [Microsoft.AspNetCore.Mvc.Testing](https://github.com/dotnet/aspnetcore) from 6.0.6 to 6.0.7. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md) - [Commits](https://github.com/dotnet/aspnetcore/commits) --- updated-dependencies: - dependency-name: Microsoft.AspNetCore.Mvc.Testing dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj b/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj index e854727b..1377f01a 100644 --- a/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj +++ b/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj @@ -7,7 +7,7 @@ - + From f81031506900a0aa02e6c164b6711710ab3e228a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Jul 2022 00:32:08 +0300 Subject: [PATCH 382/455] Bump GraphQL.Server.Transports.AspNetCore from 6.0.0 to 6.1.0 (#414) Bumps [GraphQL.Server.Transports.AspNetCore](https://github.com/graphql-dotnet/server) from 6.0.0 to 6.1.0. - [Release notes](https://github.com/graphql-dotnet/server/releases) - [Commits](https://github.com/graphql-dotnet/server/compare/6.0.0...6.1.0) --- updated-dependencies: - dependency-name: GraphQL.Server.Transports.AspNetCore dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj b/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj index bd680444..97773f9f 100644 --- a/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj +++ b/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj @@ -6,7 +6,7 @@ - + From ae7da16141a1d278a71fa75cf7a287d3b51b1aca Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Mon, 18 Jul 2022 10:32:46 +0300 Subject: [PATCH 383/455] Move IDisposable to implementation (#417) --- src/GraphQL.Client.Abstractions/IGraphQLClient.cs | 4 ++-- src/GraphQL.Client/GraphQLHttpClient.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/GraphQL.Client.Abstractions/IGraphQLClient.cs b/src/GraphQL.Client.Abstractions/IGraphQLClient.cs index 0508f8b5..40ecac8e 100644 --- a/src/GraphQL.Client.Abstractions/IGraphQLClient.cs +++ b/src/GraphQL.Client.Abstractions/IGraphQLClient.cs @@ -5,7 +5,7 @@ namespace GraphQL.Client.Abstractions { - public interface IGraphQLClient : IDisposable + public interface IGraphQLClient { Task> SendQueryAsync(GraphQLRequest request, CancellationToken cancellationToken = default); @@ -14,7 +14,7 @@ public interface IGraphQLClient : IDisposable /// /// Creates a subscription to a GraphQL server. The connection is not established until the first actual subscription is made.
/// All subscriptions made to this stream share the same hot observable.
- /// The stream must be recreated completely after an error has occured within its logic (i.e. a ) + /// The stream must be recreated completely after an error has occurred within its logic (i.e. a ) ///
/// the GraphQL request for this subscription /// an observable stream for the specified subscription diff --git a/src/GraphQL.Client/GraphQLHttpClient.cs b/src/GraphQL.Client/GraphQLHttpClient.cs index ea095fc6..8df8951e 100644 --- a/src/GraphQL.Client/GraphQLHttpClient.cs +++ b/src/GraphQL.Client/GraphQLHttpClient.cs @@ -12,7 +12,7 @@ namespace GraphQL.Client.Http { - public class GraphQLHttpClient : IGraphQLClient + public class GraphQLHttpClient : IGraphQLClient, IDisposable { private readonly Lazy _lazyHttpWebSocket; private GraphQLHttpWebSocket GraphQlHttpWebSocket => _lazyHttpWebSocket.Value; From 9ca13c232dc2c1095a9202fcd06cd2e738daebe8 Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Mon, 18 Jul 2022 12:54:06 +0300 Subject: [PATCH 384/455] Add SourceLink, embedded readme, implicit usings; refactor project properties (#416) * Add SourceLink, embedded readme, implicit usings; refactor project properties --- Directory.Build.props | 33 ++++++++++++++++ src/src.props => Directory.Build.targets | 33 +++++++++------- GraphQL.Client.sln | 11 ++---- GraphQL.Client.sln.DotSettings | 2 - examples/GraphQL.Client.Example/Program.cs | 2 +- root.props | 38 ------------------- ...aphQL.Client.Abstractions.Websocket.csproj | 2 - .../GraphQLWebSocketRequest.cs | 4 -- .../GraphQLWebSocketResponse.cs | 3 -- .../IGraphQLWebsocketJsonSerializer.cs | 3 -- .../GraphQL.Client.Abstractions.csproj | 2 - .../GraphQLClientExtensions.cs | 4 -- .../GraphQLJsonSerializerExtensions.cs | 2 - .../IGraphQLClient.cs | 3 -- .../IGraphQLJsonSerializer.cs | 4 -- .../Utilities/StringExtensions.cs | 4 +- .../Utilities/StringUtils.cs | 5 +-- .../GraphQL.Client.LocalExecution.csproj | 2 - .../GraphQLLocalExecutionClient.cs | 5 --- .../ConstantCaseEnumConverter.cs | 2 - ...raphQL.Client.Serializer.Newtonsoft.csproj | 2 - .../MapConverter.cs | 3 -- .../NewtonsoftJsonSerializer.cs | 4 -- .../ConverterHelperExtensions.cs | 1 - .../ErrorPathConverter.cs | 3 -- ...QL.Client.Serializer.SystemTextJson.csproj | 2 - .../ImmutableConverter.cs | 3 -- .../MapConverter.cs | 4 -- .../SystemTextJsonSerializer.cs | 4 -- src/GraphQL.Client/GraphQL.Client.csproj | 2 - src/GraphQL.Client/GraphQLHttpClient.cs | 8 +--- .../GraphQLHttpClientExtensions.cs | 1 - .../GraphQLHttpClientOptions.cs | 5 +-- src/GraphQL.Client/GraphQLHttpRequest.cs | 2 - .../GraphQLHttpRequestException.cs | 1 - src/GraphQL.Client/GraphQLHttpResponse.cs | 1 - .../GraphQLSubscriptionException.cs | 1 - src/GraphQL.Client/UriExtensions.cs | 2 - .../Websocket/GraphQLHttpWebSocket.cs | 5 --- .../GraphQLWebsocketConnectionException.cs | 1 - src/GraphQL.Primitives/ErrorPath.cs | 2 - .../GraphQL.Primitives.csproj | 2 - src/GraphQL.Primitives/GraphQLError.cs | 3 -- src/GraphQL.Primitives/GraphQLLocation.cs | 3 -- src/GraphQL.Primitives/GraphQLRequest.cs | 4 -- src/GraphQL.Primitives/GraphQLResponse.cs | 3 -- src/GraphQL.Primitives/Map.cs | 2 - .../.editorconfig | 0 .../Chat/AddMessageVariables.cs | 2 - .../Chat/GraphQLClientChatExtensions.cs | 2 - .../Chat/Schema/ChatQuery.cs | 3 -- .../Chat/Schema/ChatSchema.cs | 1 - .../Chat/Schema/ChatSubscriptions.cs | 2 - .../Chat/Schema/IChat.cs | 2 - .../Chat/Schema/Message.cs | 2 - .../Chat/Schema/ReceivedMessage.cs | 2 - .../Helpers/AvailableJsonSerializers.cs | 3 -- .../Helpers/CallbackMonitor.cs | 2 - .../Helpers/ConcurrentTaskWrapper.cs | 3 -- .../Helpers/MiscellaneousExtensions.cs | 2 - .../ResolveFieldContextExtensions.cs | 3 -- .../StarWars/StarWarsData.cs | 3 -- .../StarWars/StarWarsMutation.cs | 1 + .../StarWars/StarWarsQuery.cs | 2 - .../StarWars/StarWarsSchema.cs | 1 - .../StarWars/TestData/StarWarsHumans.cs | 1 - .../StarWars/Types/StarWarsCharacter.cs | 2 - .../GraphQL.Common.Tests.csproj | 15 -------- tests/GraphQL.Integration.Tests/.editorconfig | 2 - .../Helpers/IntegrationServerTestFixture.cs | 3 -- .../Helpers/WebHostHelpers.cs | 7 ---- .../QueryAndMutationTests/Base.cs | 6 --- .../UriExtensionTests.cs | 3 +- .../WebsocketTests/Base.cs | 6 --- tests/GraphQL.Primitives.Tests/.editorconfig | 2 - tests/GraphQL.Server.Test/.editorconfig | 2 - .../GraphQL.Server.Test.csproj | 1 + .../GraphQL/Models/Repository.cs | 9 ++--- tests/GraphQL.Server.Test/GraphQL/Storage.cs | 3 -- .../GraphQL.Server.Test/GraphQL/TestQuery.cs | 1 - tests/GraphQL.Server.Test/Program.cs | 4 +- tests/GraphQL.Server.Test/Startup.cs | 4 -- tests/IntegrationTestServer/.editorconfig | 2 - .../IntegrationTestServer.csproj | 1 + tests/IntegrationTestServer/Program.cs | 6 +-- tests/IntegrationTestServer/Startup.cs | 6 --- tests/tests.props | 2 - 87 files changed, 72 insertions(+), 290 deletions(-) create mode 100644 Directory.Build.props rename src/src.props => Directory.Build.targets (66%) delete mode 100644 GraphQL.Client.sln.DotSettings delete mode 100644 root.props rename tests/{GraphQL.Client.Serializer.Tests => }/.editorconfig (100%) delete mode 100644 tests/GraphQL.Common.Tests/GraphQL.Common.Tests.csproj delete mode 100644 tests/GraphQL.Integration.Tests/.editorconfig delete mode 100644 tests/GraphQL.Primitives.Tests/.editorconfig delete mode 100644 tests/GraphQL.Server.Test/.editorconfig delete mode 100644 tests/IntegrationTestServer/.editorconfig diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 00000000..3b54328d --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,33 @@ + + + + Deinok,Alexander Rose,graphql-dotnet + A GraphQL Client for .NET Standard + true + true + latest + en-US + $(NoWarn);NU5105 + $(NoWarn);1591 + annotations + logo.64x64.png + MIT + https://github.com/graphql-dotnet/graphql-client + true + GraphQL + git + true + true + + + true + embedded + enable + true + true + True + 4 + true + + + diff --git a/src/src.props b/Directory.Build.targets similarity index 66% rename from src/src.props rename to Directory.Build.targets index 37933c88..af739e1d 100644 --- a/src/src.props +++ b/Directory.Build.targets @@ -1,20 +1,12 @@ - - - - true - 8.0 + + README.md + + true - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + false false false @@ -33,4 +25,19 @@ $(GitVersion_Sha) + + $(NoWarn);1591 + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + diff --git a/GraphQL.Client.sln b/GraphQL.Client.sln index 2de94cfb..89782ecc 100644 --- a/GraphQL.Client.sln +++ b/GraphQL.Client.sln @@ -4,26 +4,21 @@ Microsoft Visual Studio Solution File, Format Version 12.00 VisualStudioVersion = 17.1.32228.430 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{47C98B55-08F1-4428-863E-2C5C876DEEFE}" - ProjectSection(SolutionItems) = preProject - src\.editorconfig = src\.editorconfig - src\src.props = src\src.props - EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{63F75859-4698-4EDE-8B70-4ACBB8BC425A}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig .gitignore = .gitignore + Directory.Build.props = Directory.Build.props + Directory.Build.targets = Directory.Build.targets dotnet-tools.json = dotnet-tools.json LICENSE.txt = LICENSE.txt examples\GraphQL.Client.Example\Program.cs = examples\GraphQL.Client.Example\Program.cs README.md = README.md - root.props = root.props + tests\tests.props = tests\tests.props EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0B0EDB0F-FF67-4B78-A8DB-B5C23E1FEE8C}" - ProjectSection(SolutionItems) = preProject - tests\tests.props = tests\tests.props - EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{6326E0E2-3F48-4BAF-80D3-47AED5EB647C}" ProjectSection(SolutionItems) = preProject diff --git a/GraphQL.Client.sln.DotSettings b/GraphQL.Client.sln.DotSettings deleted file mode 100644 index 9e5ec22f..00000000 --- a/GraphQL.Client.sln.DotSettings +++ /dev/null @@ -1,2 +0,0 @@ - - QL \ No newline at end of file diff --git a/examples/GraphQL.Client.Example/Program.cs b/examples/GraphQL.Client.Example/Program.cs index 0bfd14b1..a67114a1 100644 --- a/examples/GraphQL.Client.Example/Program.cs +++ b/examples/GraphQL.Client.Example/Program.cs @@ -7,7 +7,7 @@ namespace GraphQL.Client.Example { - public class Program + public static class Program { public static async Task Main() { diff --git a/root.props b/root.props deleted file mode 100644 index 17bd287e..00000000 --- a/root.props +++ /dev/null @@ -1,38 +0,0 @@ - - - - Deinok,Alexander Rose,graphql-dotnet - A GraphQL Client for .NET Standard - True - True - 8.0 - en-US - $(NoWarn);CS1591;NU5048;NU5105;NU5125 - annotations - icon.png - LICENSE.txt - https://github.com/graphql-dotnet/graphql-client - true - GraphQL - git - https://github.com/graphql-dotnet/graphql-client - True - 4 - - - - - PreserveNewest - true - LICENSE.txt - false - - - PreserveNewest - true - icon.png - false - - - - diff --git a/src/GraphQL.Client.Abstractions.Websocket/GraphQL.Client.Abstractions.Websocket.csproj b/src/GraphQL.Client.Abstractions.Websocket/GraphQL.Client.Abstractions.Websocket.csproj index 048dffbf..5960fbf8 100644 --- a/src/GraphQL.Client.Abstractions.Websocket/GraphQL.Client.Abstractions.Websocket.csproj +++ b/src/GraphQL.Client.Abstractions.Websocket/GraphQL.Client.Abstractions.Websocket.csproj @@ -1,7 +1,5 @@ - - Abstractions for the Websocket transport used in GraphQL.Client netstandard2.0 diff --git a/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketRequest.cs b/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketRequest.cs index f83fe5aa..1210c34a 100644 --- a/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketRequest.cs +++ b/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketRequest.cs @@ -1,7 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - namespace GraphQL.Client.Abstractions.Websocket { /// diff --git a/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketResponse.cs b/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketResponse.cs index 390834da..5de56492 100644 --- a/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketResponse.cs +++ b/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketResponse.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; - namespace GraphQL.Client.Abstractions.Websocket { /// diff --git a/src/GraphQL.Client.Abstractions.Websocket/IGraphQLWebsocketJsonSerializer.cs b/src/GraphQL.Client.Abstractions.Websocket/IGraphQLWebsocketJsonSerializer.cs index 5e3d58ec..4526d797 100644 --- a/src/GraphQL.Client.Abstractions.Websocket/IGraphQLWebsocketJsonSerializer.cs +++ b/src/GraphQL.Client.Abstractions.Websocket/IGraphQLWebsocketJsonSerializer.cs @@ -1,6 +1,3 @@ -using System.IO; -using System.Threading.Tasks; - namespace GraphQL.Client.Abstractions.Websocket { /// diff --git a/src/GraphQL.Client.Abstractions/GraphQL.Client.Abstractions.csproj b/src/GraphQL.Client.Abstractions/GraphQL.Client.Abstractions.csproj index ef5087f4..58f116f2 100644 --- a/src/GraphQL.Client.Abstractions/GraphQL.Client.Abstractions.csproj +++ b/src/GraphQL.Client.Abstractions/GraphQL.Client.Abstractions.csproj @@ -1,7 +1,5 @@ - - Abstractions for GraphQL.Client netstandard2.0 diff --git a/src/GraphQL.Client.Abstractions/GraphQLClientExtensions.cs b/src/GraphQL.Client.Abstractions/GraphQLClientExtensions.cs index 0c891bf7..379eb932 100644 --- a/src/GraphQL.Client.Abstractions/GraphQLClientExtensions.cs +++ b/src/GraphQL.Client.Abstractions/GraphQLClientExtensions.cs @@ -1,7 +1,3 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - namespace GraphQL.Client.Abstractions { public static class GraphQLClientExtensions diff --git a/src/GraphQL.Client.Abstractions/GraphQLJsonSerializerExtensions.cs b/src/GraphQL.Client.Abstractions/GraphQLJsonSerializerExtensions.cs index 9222690c..56ff68e6 100644 --- a/src/GraphQL.Client.Abstractions/GraphQLJsonSerializerExtensions.cs +++ b/src/GraphQL.Client.Abstractions/GraphQLJsonSerializerExtensions.cs @@ -1,5 +1,3 @@ -using System; - namespace GraphQL.Client.Abstractions { public static class GraphQLJsonSerializerExtensions diff --git a/src/GraphQL.Client.Abstractions/IGraphQLClient.cs b/src/GraphQL.Client.Abstractions/IGraphQLClient.cs index 40ecac8e..f289786b 100644 --- a/src/GraphQL.Client.Abstractions/IGraphQLClient.cs +++ b/src/GraphQL.Client.Abstractions/IGraphQLClient.cs @@ -1,7 +1,4 @@ -using System; using System.Net.WebSockets; -using System.Threading; -using System.Threading.Tasks; namespace GraphQL.Client.Abstractions { diff --git a/src/GraphQL.Client.Abstractions/IGraphQLJsonSerializer.cs b/src/GraphQL.Client.Abstractions/IGraphQLJsonSerializer.cs index fa21dbac..9f5f9410 100644 --- a/src/GraphQL.Client.Abstractions/IGraphQLJsonSerializer.cs +++ b/src/GraphQL.Client.Abstractions/IGraphQLJsonSerializer.cs @@ -1,7 +1,3 @@ -using System.IO; -using System.Threading; -using System.Threading.Tasks; - namespace GraphQL.Client.Abstractions { public interface IGraphQLJsonSerializer diff --git a/src/GraphQL.Client.Abstractions/Utilities/StringExtensions.cs b/src/GraphQL.Client.Abstractions/Utilities/StringExtensions.cs index 38c718a8..8aeb4cb0 100644 --- a/src/GraphQL.Client.Abstractions/Utilities/StringExtensions.cs +++ b/src/GraphQL.Client.Abstractions/Utilities/StringExtensions.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; - -namespace GraphQL.Client.Abstractions.Utilities +namespace GraphQL.Client.Abstractions.Utilities { /// /// Copied from https://github.com/jquense/StringUtils diff --git a/src/GraphQL.Client.Abstractions/Utilities/StringUtils.cs b/src/GraphQL.Client.Abstractions/Utilities/StringUtils.cs index c643fd23..57673216 100644 --- a/src/GraphQL.Client.Abstractions/Utilities/StringUtils.cs +++ b/src/GraphQL.Client.Abstractions/Utilities/StringUtils.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; namespace GraphQL.Client.Abstractions.Utilities { diff --git a/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj b/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj index 36d80714..6973829f 100644 --- a/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj +++ b/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj @@ -1,7 +1,5 @@ - - A GraphQL Client which executes the queries directly on a provided GraphQL schema using graphql-dotnet netstandard2.0 diff --git a/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs b/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs index 9a741b32..4a92e647 100644 --- a/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs +++ b/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs @@ -1,10 +1,5 @@ -using System; -using System.IO; -using System.Linq; using System.Reactive.Linq; using System.Reactive.Threading.Tasks; -using System.Threading; -using System.Threading.Tasks; using GraphQL.Client.Abstractions; using GraphQL.Types; diff --git a/src/GraphQL.Client.Serializer.Newtonsoft/ConstantCaseEnumConverter.cs b/src/GraphQL.Client.Serializer.Newtonsoft/ConstantCaseEnumConverter.cs index d618a490..476eed9c 100644 --- a/src/GraphQL.Client.Serializer.Newtonsoft/ConstantCaseEnumConverter.cs +++ b/src/GraphQL.Client.Serializer.Newtonsoft/ConstantCaseEnumConverter.cs @@ -1,5 +1,3 @@ -using System; -using System.Linq; using System.Reflection; using GraphQL.Client.Abstractions.Utilities; using Newtonsoft.Json; diff --git a/src/GraphQL.Client.Serializer.Newtonsoft/GraphQL.Client.Serializer.Newtonsoft.csproj b/src/GraphQL.Client.Serializer.Newtonsoft/GraphQL.Client.Serializer.Newtonsoft.csproj index 27801752..65aef0d0 100644 --- a/src/GraphQL.Client.Serializer.Newtonsoft/GraphQL.Client.Serializer.Newtonsoft.csproj +++ b/src/GraphQL.Client.Serializer.Newtonsoft/GraphQL.Client.Serializer.Newtonsoft.csproj @@ -1,7 +1,5 @@ - - A serializer implementation for GraphQL.Client using Newtonsoft.Json as underlying JSON library netstandard2.0 diff --git a/src/GraphQL.Client.Serializer.Newtonsoft/MapConverter.cs b/src/GraphQL.Client.Serializer.Newtonsoft/MapConverter.cs index 3caefd5f..d1dd60f4 100644 --- a/src/GraphQL.Client.Serializer.Newtonsoft/MapConverter.cs +++ b/src/GraphQL.Client.Serializer.Newtonsoft/MapConverter.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; diff --git a/src/GraphQL.Client.Serializer.Newtonsoft/NewtonsoftJsonSerializer.cs b/src/GraphQL.Client.Serializer.Newtonsoft/NewtonsoftJsonSerializer.cs index e7cae90f..6e362c08 100644 --- a/src/GraphQL.Client.Serializer.Newtonsoft/NewtonsoftJsonSerializer.cs +++ b/src/GraphQL.Client.Serializer.Newtonsoft/NewtonsoftJsonSerializer.cs @@ -1,8 +1,4 @@ -using System; -using System.IO; using System.Text; -using System.Threading; -using System.Threading.Tasks; using GraphQL.Client.Abstractions; using GraphQL.Client.Abstractions.Websocket; using Newtonsoft.Json; diff --git a/src/GraphQL.Client.Serializer.SystemTextJson/ConverterHelperExtensions.cs b/src/GraphQL.Client.Serializer.SystemTextJson/ConverterHelperExtensions.cs index fbb9036d..7afffe57 100644 --- a/src/GraphQL.Client.Serializer.SystemTextJson/ConverterHelperExtensions.cs +++ b/src/GraphQL.Client.Serializer.SystemTextJson/ConverterHelperExtensions.cs @@ -1,4 +1,3 @@ -using System; using System.Buffers; using System.Numerics; using System.Text; diff --git a/src/GraphQL.Client.Serializer.SystemTextJson/ErrorPathConverter.cs b/src/GraphQL.Client.Serializer.SystemTextJson/ErrorPathConverter.cs index b2718096..4114a17a 100644 --- a/src/GraphQL.Client.Serializer.SystemTextJson/ErrorPathConverter.cs +++ b/src/GraphQL.Client.Serializer.SystemTextJson/ErrorPathConverter.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; using System.Text.Json; using System.Text.Json.Serialization; diff --git a/src/GraphQL.Client.Serializer.SystemTextJson/GraphQL.Client.Serializer.SystemTextJson.csproj b/src/GraphQL.Client.Serializer.SystemTextJson/GraphQL.Client.Serializer.SystemTextJson.csproj index ffb763bd..e49b975d 100644 --- a/src/GraphQL.Client.Serializer.SystemTextJson/GraphQL.Client.Serializer.SystemTextJson.csproj +++ b/src/GraphQL.Client.Serializer.SystemTextJson/GraphQL.Client.Serializer.SystemTextJson.csproj @@ -1,7 +1,5 @@ - - A serializer implementation for GraphQL.Client using System.Text.Json as underlying JSON library netstandard2.0;netcoreapp3.1 diff --git a/src/GraphQL.Client.Serializer.SystemTextJson/ImmutableConverter.cs b/src/GraphQL.Client.Serializer.SystemTextJson/ImmutableConverter.cs index 7355970d..0c9ec398 100644 --- a/src/GraphQL.Client.Serializer.SystemTextJson/ImmutableConverter.cs +++ b/src/GraphQL.Client.Serializer.SystemTextJson/ImmutableConverter.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Text.Json; diff --git a/src/GraphQL.Client.Serializer.SystemTextJson/MapConverter.cs b/src/GraphQL.Client.Serializer.SystemTextJson/MapConverter.cs index e41c1bf5..e3ab7217 100644 --- a/src/GraphQL.Client.Serializer.SystemTextJson/MapConverter.cs +++ b/src/GraphQL.Client.Serializer.SystemTextJson/MapConverter.cs @@ -1,7 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; using System.Text.Json; using System.Text.Json.Serialization; diff --git a/src/GraphQL.Client.Serializer.SystemTextJson/SystemTextJsonSerializer.cs b/src/GraphQL.Client.Serializer.SystemTextJson/SystemTextJsonSerializer.cs index d0325464..05b2c2f0 100644 --- a/src/GraphQL.Client.Serializer.SystemTextJson/SystemTextJsonSerializer.cs +++ b/src/GraphQL.Client.Serializer.SystemTextJson/SystemTextJsonSerializer.cs @@ -1,9 +1,5 @@ -using System; -using System.IO; using System.Text.Json; using System.Text.Json.Serialization; -using System.Threading; -using System.Threading.Tasks; using GraphQL.Client.Abstractions; using GraphQL.Client.Abstractions.Websocket; diff --git a/src/GraphQL.Client/GraphQL.Client.csproj b/src/GraphQL.Client/GraphQL.Client.csproj index 8203c2f4..719c1a76 100644 --- a/src/GraphQL.Client/GraphQL.Client.csproj +++ b/src/GraphQL.Client/GraphQL.Client.csproj @@ -1,7 +1,5 @@ - - netstandard2.0;net461 GraphQL.Client.Http diff --git a/src/GraphQL.Client/GraphQLHttpClient.cs b/src/GraphQL.Client/GraphQLHttpClient.cs index 8df8951e..fb95a963 100644 --- a/src/GraphQL.Client/GraphQLHttpClient.cs +++ b/src/GraphQL.Client/GraphQLHttpClient.cs @@ -1,11 +1,5 @@ -using System; using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Net.Http; using System.Net.Http.Headers; -using System.Threading; -using System.Threading.Tasks; using GraphQL.Client.Abstractions; using GraphQL.Client.Abstractions.Websocket; using GraphQL.Client.Http.Websocket; @@ -110,7 +104,7 @@ public IObservable> CreateSubscriptionStream - /// explicitly opens the websocket connection. Will be closed again on disposing the last subscription + /// Explicitly opens the websocket connection. Will be closed again on disposing the last subscription. /// /// public Task InitializeWebsocketConnection() => GraphQlHttpWebSocket.InitializeWebSocket(); diff --git a/src/GraphQL.Client/GraphQLHttpClientExtensions.cs b/src/GraphQL.Client/GraphQLHttpClientExtensions.cs index 041ed5c6..c3f49dfb 100644 --- a/src/GraphQL.Client/GraphQLHttpClientExtensions.cs +++ b/src/GraphQL.Client/GraphQLHttpClientExtensions.cs @@ -1,4 +1,3 @@ -using System; using System.Net.WebSockets; using GraphQL.Client.Abstractions; diff --git a/src/GraphQL.Client/GraphQLHttpClientOptions.cs b/src/GraphQL.Client/GraphQLHttpClientOptions.cs index dac7c4f1..74cd6c08 100644 --- a/src/GraphQL.Client/GraphQLHttpClientOptions.cs +++ b/src/GraphQL.Client/GraphQLHttpClientOptions.cs @@ -1,13 +1,10 @@ -using System; -using System.Net.Http; using System.Net.Http.Headers; using System.Net.WebSockets; -using System.Threading.Tasks; namespace GraphQL.Client.Http { /// - /// The Options that the will use + /// The Options that the will use. /// public class GraphQLHttpClientOptions { diff --git a/src/GraphQL.Client/GraphQLHttpRequest.cs b/src/GraphQL.Client/GraphQLHttpRequest.cs index eb3aac14..ac62c6a8 100644 --- a/src/GraphQL.Client/GraphQLHttpRequest.cs +++ b/src/GraphQL.Client/GraphQLHttpRequest.cs @@ -1,5 +1,3 @@ -using System; -using System.Net.Http; using System.Runtime.Serialization; using System.Text; using GraphQL.Client.Abstractions; diff --git a/src/GraphQL.Client/GraphQLHttpRequestException.cs b/src/GraphQL.Client/GraphQLHttpRequestException.cs index 5b3a8b9f..faf7e6c5 100644 --- a/src/GraphQL.Client/GraphQLHttpRequestException.cs +++ b/src/GraphQL.Client/GraphQLHttpRequestException.cs @@ -1,4 +1,3 @@ -using System; using System.Net; using System.Net.Http.Headers; diff --git a/src/GraphQL.Client/GraphQLHttpResponse.cs b/src/GraphQL.Client/GraphQLHttpResponse.cs index b4b09784..8888ff61 100644 --- a/src/GraphQL.Client/GraphQLHttpResponse.cs +++ b/src/GraphQL.Client/GraphQLHttpResponse.cs @@ -1,4 +1,3 @@ -using System; using System.Net; using System.Net.Http.Headers; diff --git a/src/GraphQL.Client/GraphQLSubscriptionException.cs b/src/GraphQL.Client/GraphQLSubscriptionException.cs index d9accc85..28e8566c 100644 --- a/src/GraphQL.Client/GraphQLSubscriptionException.cs +++ b/src/GraphQL.Client/GraphQLSubscriptionException.cs @@ -1,4 +1,3 @@ -using System; using System.Runtime.Serialization; namespace GraphQL.Client.Http diff --git a/src/GraphQL.Client/UriExtensions.cs b/src/GraphQL.Client/UriExtensions.cs index 296eb052..1e2e53e2 100644 --- a/src/GraphQL.Client/UriExtensions.cs +++ b/src/GraphQL.Client/UriExtensions.cs @@ -1,5 +1,3 @@ -using System; - namespace GraphQL.Client.Http { public static class UriExtensions diff --git a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs index 67a517c6..0aa60818 100644 --- a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs +++ b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs @@ -1,7 +1,4 @@ -using System; using System.Diagnostics; -using System.IO; -using System.Net.Http; using System.Net.WebSockets; using System.Reactive; using System.Reactive.Disposables; @@ -9,8 +6,6 @@ using System.Reactive.Subjects; using System.Reactive.Threading.Tasks; using System.Text; -using System.Threading; -using System.Threading.Tasks; using GraphQL.Client.Abstractions.Websocket; namespace GraphQL.Client.Http.Websocket diff --git a/src/GraphQL.Client/Websocket/GraphQLWebsocketConnectionException.cs b/src/GraphQL.Client/Websocket/GraphQLWebsocketConnectionException.cs index c6b2e831..a1416948 100644 --- a/src/GraphQL.Client/Websocket/GraphQLWebsocketConnectionException.cs +++ b/src/GraphQL.Client/Websocket/GraphQLWebsocketConnectionException.cs @@ -1,4 +1,3 @@ -using System; using System.Runtime.Serialization; namespace GraphQL.Client.Http.Websocket diff --git a/src/GraphQL.Primitives/ErrorPath.cs b/src/GraphQL.Primitives/ErrorPath.cs index 59abdb79..dd449d73 100644 --- a/src/GraphQL.Primitives/ErrorPath.cs +++ b/src/GraphQL.Primitives/ErrorPath.cs @@ -1,5 +1,3 @@ -using System.Collections.Generic; - namespace GraphQL { public class ErrorPath : List diff --git a/src/GraphQL.Primitives/GraphQL.Primitives.csproj b/src/GraphQL.Primitives/GraphQL.Primitives.csproj index c4210d9d..676e6fe8 100644 --- a/src/GraphQL.Primitives/GraphQL.Primitives.csproj +++ b/src/GraphQL.Primitives/GraphQL.Primitives.csproj @@ -1,7 +1,5 @@ - - GraphQL basic types GraphQL diff --git a/src/GraphQL.Primitives/GraphQLError.cs b/src/GraphQL.Primitives/GraphQLError.cs index c76d3f00..7b3e9a1a 100644 --- a/src/GraphQL.Primitives/GraphQLError.cs +++ b/src/GraphQL.Primitives/GraphQLError.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; using System.Runtime.Serialization; namespace GraphQL diff --git a/src/GraphQL.Primitives/GraphQLLocation.cs b/src/GraphQL.Primitives/GraphQLLocation.cs index 9464817a..4a499031 100644 --- a/src/GraphQL.Primitives/GraphQLLocation.cs +++ b/src/GraphQL.Primitives/GraphQLLocation.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; - namespace GraphQL { /// diff --git a/src/GraphQL.Primitives/GraphQLRequest.cs b/src/GraphQL.Primitives/GraphQLRequest.cs index 2755122c..5dbb766d 100644 --- a/src/GraphQL.Primitives/GraphQLRequest.cs +++ b/src/GraphQL.Primitives/GraphQLRequest.cs @@ -1,7 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; - namespace GraphQL { /// diff --git a/src/GraphQL.Primitives/GraphQLResponse.cs b/src/GraphQL.Primitives/GraphQLResponse.cs index 1c1af7de..c4c8a010 100644 --- a/src/GraphQL.Primitives/GraphQLResponse.cs +++ b/src/GraphQL.Primitives/GraphQLResponse.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; using System.Runtime.Serialization; namespace GraphQL diff --git a/src/GraphQL.Primitives/Map.cs b/src/GraphQL.Primitives/Map.cs index 9c4f503b..b51f5a85 100644 --- a/src/GraphQL.Primitives/Map.cs +++ b/src/GraphQL.Primitives/Map.cs @@ -1,5 +1,3 @@ -using System.Collections.Generic; - namespace GraphQL { /// diff --git a/tests/GraphQL.Client.Serializer.Tests/.editorconfig b/tests/.editorconfig similarity index 100% rename from tests/GraphQL.Client.Serializer.Tests/.editorconfig rename to tests/.editorconfig diff --git a/tests/GraphQL.Client.Tests.Common/Chat/AddMessageVariables.cs b/tests/GraphQL.Client.Tests.Common/Chat/AddMessageVariables.cs index 887c55cd..916054e8 100644 --- a/tests/GraphQL.Client.Tests.Common/Chat/AddMessageVariables.cs +++ b/tests/GraphQL.Client.Tests.Common/Chat/AddMessageVariables.cs @@ -1,5 +1,3 @@ -using System; - namespace GraphQL.Client.Tests.Common.Chat { public class AddMessageVariables diff --git a/tests/GraphQL.Client.Tests.Common/Chat/GraphQLClientChatExtensions.cs b/tests/GraphQL.Client.Tests.Common/Chat/GraphQLClientChatExtensions.cs index 07648f06..47edfc28 100644 --- a/tests/GraphQL.Client.Tests.Common/Chat/GraphQLClientChatExtensions.cs +++ b/tests/GraphQL.Client.Tests.Common/Chat/GraphQLClientChatExtensions.cs @@ -1,5 +1,3 @@ -using System; -using System.Threading.Tasks; using GraphQL.Client.Abstractions; namespace GraphQL.Client.Tests.Common.Chat diff --git a/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatQuery.cs b/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatQuery.cs index e60bf97e..5645beb8 100644 --- a/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatQuery.cs +++ b/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatQuery.cs @@ -1,6 +1,3 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading; using GraphQL.Types; namespace GraphQL.Client.Tests.Common.Chat.Schema diff --git a/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatSchema.cs b/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatSchema.cs index fbdfedf1..d606cdbd 100644 --- a/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatSchema.cs +++ b/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatSchema.cs @@ -1,4 +1,3 @@ -using System; using Microsoft.Extensions.DependencyInjection; namespace GraphQL.Client.Tests.Common.Chat.Schema diff --git a/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatSubscriptions.cs b/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatSubscriptions.cs index f3fbab63..58dcd240 100644 --- a/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatSubscriptions.cs +++ b/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatSubscriptions.cs @@ -1,5 +1,3 @@ -using System; -using System.Linq; using System.Reactive.Linq; using System.Security.Claims; using GraphQL.Resolvers; diff --git a/tests/GraphQL.Client.Tests.Common/Chat/Schema/IChat.cs b/tests/GraphQL.Client.Tests.Common/Chat/Schema/IChat.cs index 63aaf439..bfbdeb32 100644 --- a/tests/GraphQL.Client.Tests.Common/Chat/Schema/IChat.cs +++ b/tests/GraphQL.Client.Tests.Common/Chat/Schema/IChat.cs @@ -1,10 +1,8 @@ -using System; using System.Collections.Concurrent; using System.Diagnostics; using System.Reactive.Disposables; using System.Reactive.Linq; using System.Reactive.Subjects; -using System.Threading; namespace GraphQL.Client.Tests.Common.Chat.Schema { diff --git a/tests/GraphQL.Client.Tests.Common/Chat/Schema/Message.cs b/tests/GraphQL.Client.Tests.Common/Chat/Schema/Message.cs index 345a397e..e344caee 100644 --- a/tests/GraphQL.Client.Tests.Common/Chat/Schema/Message.cs +++ b/tests/GraphQL.Client.Tests.Common/Chat/Schema/Message.cs @@ -1,5 +1,3 @@ -using System; - namespace GraphQL.Client.Tests.Common.Chat.Schema { public class Message diff --git a/tests/GraphQL.Client.Tests.Common/Chat/Schema/ReceivedMessage.cs b/tests/GraphQL.Client.Tests.Common/Chat/Schema/ReceivedMessage.cs index 9cf72c9b..95d7d295 100644 --- a/tests/GraphQL.Client.Tests.Common/Chat/Schema/ReceivedMessage.cs +++ b/tests/GraphQL.Client.Tests.Common/Chat/Schema/ReceivedMessage.cs @@ -1,5 +1,3 @@ -using System; - namespace GraphQL.Client.Tests.Common.Chat.Schema { public class ReceivedMessage diff --git a/tests/GraphQL.Client.Tests.Common/Helpers/AvailableJsonSerializers.cs b/tests/GraphQL.Client.Tests.Common/Helpers/AvailableJsonSerializers.cs index 4104fd20..dca19832 100644 --- a/tests/GraphQL.Client.Tests.Common/Helpers/AvailableJsonSerializers.cs +++ b/tests/GraphQL.Client.Tests.Common/Helpers/AvailableJsonSerializers.cs @@ -1,7 +1,4 @@ -using System; using System.Collections; -using System.Collections.Generic; -using System.Linq; using GraphQL.Client.Abstractions; namespace GraphQL.Client.Tests.Common.Helpers diff --git a/tests/GraphQL.Client.Tests.Common/Helpers/CallbackMonitor.cs b/tests/GraphQL.Client.Tests.Common/Helpers/CallbackMonitor.cs index 54e51c78..3603041c 100644 --- a/tests/GraphQL.Client.Tests.Common/Helpers/CallbackMonitor.cs +++ b/tests/GraphQL.Client.Tests.Common/Helpers/CallbackMonitor.cs @@ -1,6 +1,4 @@ -using System; using System.Diagnostics; -using System.Threading; using FluentAssertions; using FluentAssertions.Execution; using FluentAssertions.Primitives; diff --git a/tests/GraphQL.Client.Tests.Common/Helpers/ConcurrentTaskWrapper.cs b/tests/GraphQL.Client.Tests.Common/Helpers/ConcurrentTaskWrapper.cs index c16e1744..6d957c38 100644 --- a/tests/GraphQL.Client.Tests.Common/Helpers/ConcurrentTaskWrapper.cs +++ b/tests/GraphQL.Client.Tests.Common/Helpers/ConcurrentTaskWrapper.cs @@ -1,6 +1,3 @@ -using System; -using System.Threading.Tasks; - namespace GraphQL.Client.Tests.Common.Helpers { public class ConcurrentTaskWrapper diff --git a/tests/GraphQL.Client.Tests.Common/Helpers/MiscellaneousExtensions.cs b/tests/GraphQL.Client.Tests.Common/Helpers/MiscellaneousExtensions.cs index dcbb6db9..428d4cca 100644 --- a/tests/GraphQL.Client.Tests.Common/Helpers/MiscellaneousExtensions.cs +++ b/tests/GraphQL.Client.Tests.Common/Helpers/MiscellaneousExtensions.cs @@ -1,5 +1,3 @@ -using System.Linq; -using System.Threading.Tasks; using GraphQL.Client.Http; namespace GraphQL.Client.Tests.Common.Helpers diff --git a/tests/GraphQL.Client.Tests.Common/StarWars/Extensions/ResolveFieldContextExtensions.cs b/tests/GraphQL.Client.Tests.Common/StarWars/Extensions/ResolveFieldContextExtensions.cs index 44ba5895..ff3a12da 100644 --- a/tests/GraphQL.Client.Tests.Common/StarWars/Extensions/ResolveFieldContextExtensions.cs +++ b/tests/GraphQL.Client.Tests.Common/StarWars/Extensions/ResolveFieldContextExtensions.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; using GraphQL.Builders; using GraphQL.Client.Tests.Common.StarWars.Types; using GraphQL.Types.Relay.DataObjects; diff --git a/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsData.cs b/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsData.cs index f561ed87..c0d3595b 100644 --- a/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsData.cs +++ b/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsData.cs @@ -1,6 +1,3 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using GraphQL.Client.Tests.Common.StarWars.Types; namespace GraphQL.Client.Tests.Common.StarWars diff --git a/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsMutation.cs b/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsMutation.cs index cd577688..130ee1a9 100644 --- a/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsMutation.cs +++ b/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsMutation.cs @@ -3,6 +3,7 @@ namespace GraphQL.Client.Tests.Common.StarWars { + /// Mutation graph type for StarWars schema. /// /// This is an example JSON request for a mutation /// { diff --git a/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsQuery.cs b/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsQuery.cs index ee2b9d3d..38b29e6f 100644 --- a/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsQuery.cs +++ b/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsQuery.cs @@ -1,5 +1,3 @@ -using System; -using System.Threading.Tasks; using GraphQL.Client.Tests.Common.StarWars.Types; using GraphQL.Types; diff --git a/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsSchema.cs b/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsSchema.cs index eff7cff4..3756f471 100644 --- a/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsSchema.cs +++ b/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsSchema.cs @@ -1,4 +1,3 @@ -using System; using GraphQL.Types; using Microsoft.Extensions.DependencyInjection; diff --git a/tests/GraphQL.Client.Tests.Common/StarWars/TestData/StarWarsHumans.cs b/tests/GraphQL.Client.Tests.Common/StarWars/TestData/StarWarsHumans.cs index 1e93ccb6..25376b87 100644 --- a/tests/GraphQL.Client.Tests.Common/StarWars/TestData/StarWarsHumans.cs +++ b/tests/GraphQL.Client.Tests.Common/StarWars/TestData/StarWarsHumans.cs @@ -1,5 +1,4 @@ using System.Collections; -using System.Collections.Generic; namespace GraphQL.Client.Tests.Common.StarWars.TestData { diff --git a/tests/GraphQL.Client.Tests.Common/StarWars/Types/StarWarsCharacter.cs b/tests/GraphQL.Client.Tests.Common/StarWars/Types/StarWarsCharacter.cs index b39d4cdc..6c646e0b 100644 --- a/tests/GraphQL.Client.Tests.Common/StarWars/Types/StarWarsCharacter.cs +++ b/tests/GraphQL.Client.Tests.Common/StarWars/Types/StarWarsCharacter.cs @@ -1,5 +1,3 @@ -using System.Collections.Generic; - namespace GraphQL.Client.Tests.Common.StarWars.Types { public abstract class StarWarsCharacter diff --git a/tests/GraphQL.Common.Tests/GraphQL.Common.Tests.csproj b/tests/GraphQL.Common.Tests/GraphQL.Common.Tests.csproj deleted file mode 100644 index 4aae0487..00000000 --- a/tests/GraphQL.Common.Tests/GraphQL.Common.Tests.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - true - netcoreapp3.0 - - - - - - - diff --git a/tests/GraphQL.Integration.Tests/.editorconfig b/tests/GraphQL.Integration.Tests/.editorconfig deleted file mode 100644 index d1655ff8..00000000 --- a/tests/GraphQL.Integration.Tests/.editorconfig +++ /dev/null @@ -1,2 +0,0 @@ -# Configure await -configure_await_analysis_mode = disabled diff --git a/tests/GraphQL.Integration.Tests/Helpers/IntegrationServerTestFixture.cs b/tests/GraphQL.Integration.Tests/Helpers/IntegrationServerTestFixture.cs index 25773b47..59dc0733 100644 --- a/tests/GraphQL.Integration.Tests/Helpers/IntegrationServerTestFixture.cs +++ b/tests/GraphQL.Integration.Tests/Helpers/IntegrationServerTestFixture.cs @@ -1,12 +1,9 @@ -using System; -using System.Threading.Tasks; using GraphQL.Client.Abstractions.Websocket; using GraphQL.Client.Http; using GraphQL.Client.Serializer.Newtonsoft; using GraphQL.Client.Serializer.SystemTextJson; using GraphQL.Client.Tests.Common; using GraphQL.Client.Tests.Common.Helpers; -using Microsoft.AspNetCore.Hosting; namespace GraphQL.Integration.Tests.Helpers { diff --git a/tests/GraphQL.Integration.Tests/Helpers/WebHostHelpers.cs b/tests/GraphQL.Integration.Tests/Helpers/WebHostHelpers.cs index 0bdb0e8c..fb3ba46b 100644 --- a/tests/GraphQL.Integration.Tests/Helpers/WebHostHelpers.cs +++ b/tests/GraphQL.Integration.Tests/Helpers/WebHostHelpers.cs @@ -1,16 +1,9 @@ -using System; -using System.Threading.Tasks; using GraphQL.Client.Abstractions.Websocket; using GraphQL.Client.Http; using GraphQL.Client.Serializer.Newtonsoft; using GraphQL.Client.Tests.Common; using GraphQL.Client.Tests.Common.Helpers; using IntegrationTestServer; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; namespace GraphQL.Integration.Tests.Helpers { diff --git a/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs b/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs index 249ef8e7..e489046a 100644 --- a/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs +++ b/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs @@ -1,17 +1,11 @@ -using System; using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; using FluentAssertions; using GraphQL.Client.Abstractions; using GraphQL.Client.Http; using GraphQL.Client.Tests.Common.Chat.Schema; using GraphQL.Client.Tests.Common.Helpers; -using GraphQL.Client.Tests.Common.StarWars; using GraphQL.Client.Tests.Common.StarWars.TestData; using GraphQL.Integration.Tests.Helpers; -using Microsoft.Extensions.DependencyInjection; using Xunit; namespace GraphQL.Integration.Tests.QueryAndMutationTests diff --git a/tests/GraphQL.Integration.Tests/UriExtensionTests.cs b/tests/GraphQL.Integration.Tests/UriExtensionTests.cs index 1298ca11..4daba07c 100644 --- a/tests/GraphQL.Integration.Tests/UriExtensionTests.cs +++ b/tests/GraphQL.Integration.Tests/UriExtensionTests.cs @@ -1,5 +1,4 @@ -using System; -using FluentAssertions; +using FluentAssertions; using GraphQL.Client.Http; using Xunit; diff --git a/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs b/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs index 8bc9cc7f..a63ed980 100644 --- a/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs +++ b/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs @@ -1,11 +1,6 @@ -using System; using System.Collections.Concurrent; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; using System.Net.WebSockets; -using System.Threading; -using System.Threading.Tasks; using FluentAssertions; using FluentAssertions.Execution; using FluentAssertions.Extensions; @@ -17,7 +12,6 @@ using GraphQL.Client.Tests.Common.Chat.Schema; using GraphQL.Client.Tests.Common.Helpers; using GraphQL.Integration.Tests.Helpers; -using Microsoft.Extensions.DependencyInjection; using Xunit; using Xunit.Abstractions; diff --git a/tests/GraphQL.Primitives.Tests/.editorconfig b/tests/GraphQL.Primitives.Tests/.editorconfig deleted file mode 100644 index d1655ff8..00000000 --- a/tests/GraphQL.Primitives.Tests/.editorconfig +++ /dev/null @@ -1,2 +0,0 @@ -# Configure await -configure_await_analysis_mode = disabled diff --git a/tests/GraphQL.Server.Test/.editorconfig b/tests/GraphQL.Server.Test/.editorconfig deleted file mode 100644 index d1655ff8..00000000 --- a/tests/GraphQL.Server.Test/.editorconfig +++ /dev/null @@ -1,2 +0,0 @@ -# Configure await -configure_await_analysis_mode = disabled diff --git a/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj b/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj index 97773f9f..ac59ef1a 100644 --- a/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj +++ b/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj @@ -2,6 +2,7 @@ net6 + false diff --git a/tests/GraphQL.Server.Test/GraphQL/Models/Repository.cs b/tests/GraphQL.Server.Test/GraphQL/Models/Repository.cs index 95e5f1fc..b262a6bd 100644 --- a/tests/GraphQL.Server.Test/GraphQL/Models/Repository.cs +++ b/tests/GraphQL.Server.Test/GraphQL/Models/Repository.cs @@ -1,4 +1,3 @@ -using System; using GraphQL.Types; namespace GraphQL.Server.Test.GraphQL.Models @@ -7,13 +6,13 @@ public class Repository { public int DatabaseId { get; set; } - public string Id { get; set; } + public string? Id { get; set; } - public string Name { get; set; } + public string? Name { get; set; } - public object Owner { get; set; } + public object? Owner { get; set; } - public Uri Url { get; set; } + public Uri? Url { get; set; } } public class RepositoryGraphType : ObjectGraphType diff --git a/tests/GraphQL.Server.Test/GraphQL/Storage.cs b/tests/GraphQL.Server.Test/GraphQL/Storage.cs index 0280b28f..f157da14 100644 --- a/tests/GraphQL.Server.Test/GraphQL/Storage.cs +++ b/tests/GraphQL.Server.Test/GraphQL/Storage.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; using GraphQL.Server.Test.GraphQL.Models; namespace GraphQL.Server.Test.GraphQL diff --git a/tests/GraphQL.Server.Test/GraphQL/TestQuery.cs b/tests/GraphQL.Server.Test/GraphQL/TestQuery.cs index bf8aa3ae..4eb32723 100644 --- a/tests/GraphQL.Server.Test/GraphQL/TestQuery.cs +++ b/tests/GraphQL.Server.Test/GraphQL/TestQuery.cs @@ -1,4 +1,3 @@ -using System.Linq; using GraphQL.Server.Test.GraphQL.Models; using GraphQL.Types; diff --git a/tests/GraphQL.Server.Test/Program.cs b/tests/GraphQL.Server.Test/Program.cs index df18d888..98538f1f 100644 --- a/tests/GraphQL.Server.Test/Program.cs +++ b/tests/GraphQL.Server.Test/Program.cs @@ -1,10 +1,8 @@ -using System.Threading.Tasks; using Microsoft.AspNetCore; -using Microsoft.AspNetCore.Hosting; namespace GraphQL.Server.Test { - public class Program + public static class Program { public static async Task Main(string[] args) => await CreateHostBuilder(args).Build().RunAsync(); diff --git a/tests/GraphQL.Server.Test/Startup.cs b/tests/GraphQL.Server.Test/Startup.cs index 06eae89b..2421cf70 100644 --- a/tests/GraphQL.Server.Test/Startup.cs +++ b/tests/GraphQL.Server.Test/Startup.cs @@ -1,10 +1,6 @@ using GraphQL.MicrosoftDI; using GraphQL.Server.Test.GraphQL; using GraphQL.Server.Ui.GraphiQL; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; namespace GraphQL.Server.Test { diff --git a/tests/IntegrationTestServer/.editorconfig b/tests/IntegrationTestServer/.editorconfig deleted file mode 100644 index d1655ff8..00000000 --- a/tests/IntegrationTestServer/.editorconfig +++ /dev/null @@ -1,2 +0,0 @@ -# Configure await -configure_await_analysis_mode = disabled diff --git a/tests/IntegrationTestServer/IntegrationTestServer.csproj b/tests/IntegrationTestServer/IntegrationTestServer.csproj index 1e68549b..4bda2103 100644 --- a/tests/IntegrationTestServer/IntegrationTestServer.csproj +++ b/tests/IntegrationTestServer/IntegrationTestServer.csproj @@ -3,6 +3,7 @@ net6 IntegrationTestServer.Program + false diff --git a/tests/IntegrationTestServer/Program.cs b/tests/IntegrationTestServer/Program.cs index 09269167..ac56ea87 100644 --- a/tests/IntegrationTestServer/Program.cs +++ b/tests/IntegrationTestServer/Program.cs @@ -1,16 +1,14 @@ using Microsoft.AspNetCore; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Logging; namespace IntegrationTestServer { - public class Program + public static class Program { public static void Main(string[] args) => CreateWebHostBuilder(args).Build().Run(); public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup() - .ConfigureLogging((ctx, logging) => logging.SetMinimumLevel(LogLevel.Debug)); + .ConfigureLogging((_, logging) => logging.SetMinimumLevel(LogLevel.Debug)); } } diff --git a/tests/IntegrationTestServer/Startup.cs b/tests/IntegrationTestServer/Startup.cs index 0d1ad68e..c8441472 100644 --- a/tests/IntegrationTestServer/Startup.cs +++ b/tests/IntegrationTestServer/Startup.cs @@ -8,13 +8,7 @@ using GraphQL.Server.Ui.GraphiQL; using GraphQL.SystemTextJson; using GraphQL.Types; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Server.Kestrel.Core; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; namespace IntegrationTestServer { diff --git a/tests/tests.props b/tests/tests.props index f2157f5f..28e5e65c 100644 --- a/tests/tests.props +++ b/tests/tests.props @@ -1,7 +1,5 @@ - - false $(NoWarn);NU1701;IDE1006 From ac2043a577f33906fc1d295dce1dd9c76e4063dd Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Wed, 20 Jul 2022 01:36:16 +0300 Subject: [PATCH 385/455] Allow non-200 HTTP status codes (#419) * Allow non-200 HTTP status codes * Update src/GraphQL.Client/GraphQLHttpClientOptions.cs Co-authored-by: Alexander Rose * fix Co-authored-by: Alexander Rose --- src/GraphQL.Client/GraphQLHttpClient.cs | 19 +++++++++++-------- .../GraphQLHttpClientOptions.cs | 12 +++++++++--- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/GraphQL.Client/GraphQLHttpClient.cs b/src/GraphQL.Client/GraphQLHttpClient.cs index fb95a963..a0b20c5d 100644 --- a/src/GraphQL.Client/GraphQLHttpClient.cs +++ b/src/GraphQL.Client/GraphQLHttpClient.cs @@ -11,7 +11,7 @@ public class GraphQLHttpClient : IGraphQLClient, IDisposable private readonly Lazy _lazyHttpWebSocket; private GraphQLHttpWebSocket GraphQlHttpWebSocket => _lazyHttpWebSocket.Value; - private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); + private readonly CancellationTokenSource _cancellationTokenSource = new(); private readonly bool _disposeHttpClient = false; @@ -42,14 +42,17 @@ public class GraphQLHttpClient : IGraphQLClient, IDisposable #region Constructors - public GraphQLHttpClient(string endPoint, IGraphQLWebsocketJsonSerializer serializer) : this(new Uri(endPoint), serializer) { } + public GraphQLHttpClient(string endPoint, IGraphQLWebsocketJsonSerializer serializer) + : this(new Uri(endPoint), serializer) { } - public GraphQLHttpClient(Uri endPoint, IGraphQLWebsocketJsonSerializer serializer) : this(o => o.EndPoint = endPoint, serializer) { } + public GraphQLHttpClient(Uri endPoint, IGraphQLWebsocketJsonSerializer serializer) + : this(o => o.EndPoint = endPoint, serializer) { } - public GraphQLHttpClient(Action configure, IGraphQLWebsocketJsonSerializer serializer) : this(configure.New(), serializer) { } + public GraphQLHttpClient(Action configure, IGraphQLWebsocketJsonSerializer serializer) + : this(configure.New(), serializer) { } - public GraphQLHttpClient(GraphQLHttpClientOptions options, IGraphQLWebsocketJsonSerializer serializer) : this( - options, serializer, new HttpClient(options.HttpMessageHandler)) + public GraphQLHttpClient(GraphQLHttpClientOptions options, IGraphQLWebsocketJsonSerializer serializer) + : this(options, serializer, new HttpClient(options.HttpMessageHandler)) { // set this flag to dispose the internally created HttpClient when GraphQLHttpClient gets disposed _disposeHttpClient = true; @@ -120,7 +123,7 @@ private async Task> SendHttpRequestAsync(contentStream, cancellationToken).ConfigureAwait(false); return graphQLResponse.ToGraphQLHttpResponse(httpResponseMessage.Headers, httpResponseMessage.StatusCode); @@ -167,7 +170,7 @@ public void Dispose() } private volatile bool _disposed; - private readonly object _disposeLocker = new object(); + private readonly object _disposeLocker = new(); protected virtual void Dispose(bool disposing) { diff --git a/src/GraphQL.Client/GraphQLHttpClientOptions.cs b/src/GraphQL.Client/GraphQLHttpClientOptions.cs index 74cd6c08..acbdfaab 100644 --- a/src/GraphQL.Client/GraphQLHttpClientOptions.cs +++ b/src/GraphQL.Client/GraphQLHttpClientOptions.cs @@ -1,3 +1,4 @@ +using System.Net; using System.Net.Http.Headers; using System.Net.WebSockets; @@ -16,7 +17,7 @@ public class GraphQLHttpClientOptions /// /// The GraphQL EndPoint to be used for websocket connections /// - public Uri? WebSocketEndPoint { get; set; } = null; + public Uri? WebSocketEndPoint { get; set; } /// /// The that is going to be used @@ -50,12 +51,17 @@ public class GraphQLHttpClientOptions Task.FromResult(request is GraphQLHttpRequest graphQLHttpRequest ? graphQLHttpRequest : new GraphQLHttpRequest(request)); /// - /// This callback is called after successfully establishing a websocket connection but before any regular request is made. + /// Delegate to determine if GraphQL response may be properly deserialized into . + /// + public Func IsValidResponseToDeserialize { get; set; } = r => r.IsSuccessStatusCode || r.StatusCode == HttpStatusCode.BadRequest; + + /// + /// This callback is called after successfully establishing a websocket connection but before any regular request is made. /// public Func OnWebsocketConnected { get; set; } = client => Task.CompletedTask; /// - /// Configure additional websocket options (i.e. headers). This will not be invoked on Windows 7 when targeting .NET Framework 4.x. + /// Configure additional websocket options (i.e. headers). This will not be invoked on Windows 7 when targeting .NET Framework 4.x. /// public Action ConfigureWebsocketOptions { get; set; } = options => { }; From 8961dc3b0a6ba7d85d222bfd56e2b6c762d6dd16 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Jul 2022 07:05:55 +0300 Subject: [PATCH 386/455] Bump GraphQL.SystemTextJson from 5.3.2 to 5.3.3 (#426) Bumps [GraphQL.SystemTextJson](https://github.com/graphql-dotnet/graphql-dotnet) from 5.3.2 to 5.3.3. - [Release notes](https://github.com/graphql-dotnet/graphql-dotnet/releases) - [Commits](https://github.com/graphql-dotnet/graphql-dotnet/compare/5.3.2...5.3.3) --- updated-dependencies: - dependency-name: GraphQL.SystemTextJson dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../GraphQL.Client.Serializer.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj b/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj index 49a25c09..e04fc48f 100644 --- a/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj +++ b/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj @@ -8,7 +8,7 @@ - + From 04112cd9f31443b577e18dfef73508b5458152ae Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Jul 2022 07:26:40 +0300 Subject: [PATCH 387/455] Bump GraphQL.NewtonsoftJson from 5.3.2 to 5.3.3 (#425) Bumps [GraphQL.NewtonsoftJson](https://github.com/graphql-dotnet/graphql-dotnet) from 5.3.2 to 5.3.3. - [Release notes](https://github.com/graphql-dotnet/graphql-dotnet/releases) - [Commits](https://github.com/graphql-dotnet/graphql-dotnet/compare/5.3.2...5.3.3) --- updated-dependencies: - dependency-name: GraphQL.NewtonsoftJson dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ivan Maximov --- .../GraphQL.Client.Serializer.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj b/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj index e04fc48f..2e15b077 100644 --- a/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj +++ b/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj @@ -7,7 +7,7 @@ - + From 758ede0e1773dafc9f9c662e495e89fe852a9786 Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Thu, 21 Jul 2022 09:05:27 +0300 Subject: [PATCH 388/455] Convert to file-scoped namespaces, no code changes (#428) --- .editorconfig | 17 +- .../PersonAndFilmsResponse.cs | 27 +- examples/GraphQL.Client.Example/Program.cs | 49 +- .../GraphQLWebSocketMessageType.cs | 149 ++- .../GraphQLWebSocketRequest.cs | 173 ++- .../GraphQLWebSocketResponse.cs | 143 ++- .../GraphQLWebsocketConnectionState.cs | 13 +- .../IGraphQLWebsocketJsonSerializer.cs | 21 +- .../WebsocketMessageWrapper.cs | 11 +- .../GraphQLClientExtensions.cs | 83 +- .../GraphQLJsonSerializerExtensions.cs | 19 +- .../IGraphQLClient.cs | 49 +- .../IGraphQLJsonSerializer.cs | 11 +- .../Utilities/StringExtensions.cs | 37 +- .../Utilities/StringUtils.cs | 347 +++--- .../GraphQLLocalExecutionClient.cs | 145 ++- .../ServiceCollectionExtensions.cs | 15 +- .../ConstantCaseEnumConverter.cs | 37 +- .../MapConverter.cs | 89 +- .../NewtonsoftJsonSerializer.cs | 71 +- .../ConstantCaseJsonNamingPolicy.cs | 9 +- .../ConverterHelperExtensions.cs | 47 +- .../ErrorPathConverter.cs | 67 +- .../ImmutableConverter.cs | 273 +++-- .../JsonSerializerOptionsExtensions.cs | 13 +- .../MapConverter.cs | 117 +- .../SystemTextJsonSerializer.cs | 61 +- src/GraphQL.Client/GraphQLHttpClient.cs | 271 ++-- .../GraphQLHttpClientExtensions.cs | 73 +- .../GraphQLHttpClientOptions.cs | 111 +- src/GraphQL.Client/GraphQLHttpRequest.cs | 63 +- .../GraphQLHttpRequestException.cs | 56 +- src/GraphQL.Client/GraphQLHttpResponse.cs | 49 +- .../GraphQLSubscriptionException.cs | 41 +- src/GraphQL.Client/UriExtensions.cs | 61 +- .../Websocket/GraphQLHttpWebSocket.cs | 1085 ++++++++--------- .../GraphQLWebsocketConnectionException.cs | 29 +- src/GraphQL.Primitives/ErrorPath.cs | 15 +- src/GraphQL.Primitives/GraphQLError.cs | 179 ++- src/GraphQL.Primitives/GraphQLLocation.cs | 107 +- src/GraphQL.Primitives/GraphQLRequest.cs | 191 ++- src/GraphQL.Primitives/GraphQLResponse.cs | 123 +- src/GraphQL.Primitives/IGraphQLResponse.cs | 13 +- src/GraphQL.Primitives/Map.cs | 13 +- .../BaseSerializeNoCamelCaseTest.cs | 89 +- .../BaseSerializerTest.cs | 255 ++-- .../ConsistencyTests.cs | 55 +- .../NewtonsoftSerializerTest.cs | 21 +- .../SystemTextJsonSerializerTests.cs | 21 +- .../TestData/DeserializeResponseTestData.cs | 169 ++- .../TestData/SerializeToBytesTestData.cs | 45 +- .../TestData/SerializeToStringTestData.cs | 61 +- .../Chat/AddMessageMutationResult.cs | 15 +- .../Chat/AddMessageVariables.cs | 19 +- .../Chat/GraphQLClientChatExtensions.cs | 49 +- .../Chat/JoinDeveloperMutationResult.cs | 17 +- .../Chat/Schema/CapitalizedFieldsGraphType.cs | 17 +- .../Chat/Schema/ChatMutation.cs | 63 +- .../Chat/Schema/ChatQuery.cs | 73 +- .../Chat/Schema/ChatSchema.cs | 17 +- .../Chat/Schema/ChatSubscriptions.cs | 147 ++- .../Chat/Schema/IChat.cs | 155 ++- .../Chat/Schema/Message.cs | 15 +- .../Chat/Schema/MessageFrom.cs | 11 +- .../Chat/Schema/MessageFromType.cs | 13 +- .../Chat/Schema/MessageType.cs | 27 +- .../Chat/Schema/ReceivedMessage.cs | 13 +- tests/GraphQL.Client.Tests.Common/Common.cs | 83 +- .../Helpers/AvailableJsonSerializers.cs | 29 +- .../Helpers/CallbackMonitor.cs | 139 ++- .../Helpers/ConcurrentTaskWrapper.cs | 75 +- .../Helpers/MiscellaneousExtensions.cs | 33 +- .../Helpers/NetworkHelpers.cs | 19 +- .../ResolveFieldContextExtensions.cs | 89 +- .../StarWars/StarWarsData.cs | 139 ++- .../StarWars/StarWarsMutation.cs | 55 +- .../StarWars/StarWarsQuery.cs | 43 +- .../StarWars/StarWarsSchema.cs | 17 +- .../StarWars/TestData/StarWarsHumans.cs | 23 +- .../StarWars/Types/CharacterInterface.cs | 21 +- .../StarWars/Types/DroidType.cs | 35 +- .../StarWars/Types/EpisodeEnum.cs | 31 +- .../StarWars/Types/HumanInputType.cs | 15 +- .../StarWars/Types/HumanType.cs | 33 +- .../StarWars/Types/StarWarsCharacter.cs | 33 +- .../Helpers/IntegrationServerTestFixture.cs | 91 +- .../Helpers/WebHostHelpers.cs | 87 +- .../QueryAndMutationTests/Base.cs | 295 +++-- .../QueryAndMutationTests/Newtonsoft.cs | 9 +- .../QueryAndMutationTests/SystemTextJson.cs | 9 +- .../UriExtensionTests.cs | 67 +- .../WebsocketTests/Base.cs | 751 ++++++------ .../WebsocketTests/Newtonsoft.cs | 9 +- .../WebsocketTests/SystemTextJson.cs | 9 +- .../GraphQLLocationTest.cs | 111 +- .../GraphQLRequestTest.cs | 307 +++-- .../GraphQLResponseTest.cs | 189 ++- .../JsonSerializationTests.cs | 43 +- .../GraphQL/Models/Repository.cs | 37 +- tests/GraphQL.Server.Test/GraphQL/Storage.cs | 27 +- .../GraphQL/TestMutation.cs | 9 +- .../GraphQL.Server.Test/GraphQL/TestQuery.cs | 19 +- .../GraphQL.Server.Test/GraphQL/TestSchema.cs | 15 +- .../GraphQL/TestSubscription.cs | 9 +- tests/GraphQL.Server.Test/Program.cs | 19 +- tests/GraphQL.Server.Test/Startup.cs | 45 +- tests/IntegrationTestServer/Program.cs | 17 +- tests/IntegrationTestServer/Startup.cs | 101 +- 108 files changed, 4511 insertions(+), 4616 deletions(-) diff --git a/.editorconfig b/.editorconfig index 33d7fdc4..b3d87c9d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -35,7 +35,7 @@ insert_final_newline = false # Code files [*.{cs,vb}] - + # .NET code style settings - "This." and "Me." qualifiers # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#this-and-me dotnet_style_qualification_for_field = false:warning @@ -194,22 +194,25 @@ csharp_space_between_square_brackets = false csharp_preserve_single_line_blocks = true csharp_preserve_single_line_statements = false +# C# formatting settings - Namespace options +csharp_style_namespace_declarations = file_scoped:suggestion + ########## name all private fields using camelCase with underscore prefix ########## # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-naming-conventions?view=vs-2019 # dotnet_naming_rule..symbols = dotnet_naming_rule.private_fields_with_underscore.symbols = private_fields - + # dotnet_naming_symbols.. = dotnet_naming_symbols.private_fields.applicable_kinds = field dotnet_naming_symbols.private_fields.applicable_accessibilities = private - + # dotnet_naming_rule..style = dotnet_naming_rule.private_fields_with_underscore.style = prefix_underscore - + # dotnet_naming_style.. = dotnet_naming_style.prefix_underscore.capitalization = camel_case dotnet_naming_style.prefix_underscore.required_prefix = _ - + # dotnet_naming_rule..severity = dotnet_naming_rule.private_fields_with_underscore.severity = warning @@ -248,8 +251,8 @@ dotnet_naming_rule.async_methods_end_in_async.style = end_in_async_style # dotnet_naming_style.. = dotnet_naming_style.end_in_async_style.capitalization = pascal_case -dotnet_naming_style.end_in_async_style.word_separator = -dotnet_naming_style.end_in_async_style.required_prefix = +dotnet_naming_style.end_in_async_style.word_separator = +dotnet_naming_style.end_in_async_style.required_prefix = dotnet_naming_style.end_in_async_style.required_suffix = Async # dotnet_naming_rule..severity = diff --git a/examples/GraphQL.Client.Example/PersonAndFilmsResponse.cs b/examples/GraphQL.Client.Example/PersonAndFilmsResponse.cs index eafba6d1..7dbd22b5 100644 --- a/examples/GraphQL.Client.Example/PersonAndFilmsResponse.cs +++ b/examples/GraphQL.Client.Example/PersonAndFilmsResponse.cs @@ -1,25 +1,24 @@ using System.Collections.Generic; -namespace GraphQL.Client.Example +namespace GraphQL.Client.Example; + +public class PersonAndFilmsResponse { - public class PersonAndFilmsResponse + public PersonContent Person { get; set; } + + public class PersonContent { - public PersonContent Person { get; set; } + public string Name { get; set; } - public class PersonContent - { - public string Name { get; set; } + public FilmConnectionContent FilmConnection { get; set; } - public FilmConnectionContent FilmConnection { get; set; } + public class FilmConnectionContent + { + public List Films { get; set; } - public class FilmConnectionContent + public class FilmContent { - public List Films { get; set; } - - public class FilmContent - { - public string Title { get; set; } - } + public string Title { get; set; } } } } diff --git a/examples/GraphQL.Client.Example/Program.cs b/examples/GraphQL.Client.Example/Program.cs index a67114a1..b1f4d2ac 100644 --- a/examples/GraphQL.Client.Example/Program.cs +++ b/examples/GraphQL.Client.Example/Program.cs @@ -5,17 +5,17 @@ using GraphQL.Client.Http; using GraphQL.Client.Serializer.Newtonsoft; -namespace GraphQL.Client.Example +namespace GraphQL.Client.Example; + +public static class Program { - public static class Program + public static async Task Main() { - public static async Task Main() - { - using var graphQLClient = new GraphQLHttpClient("https://swapi.apis.guru/", new NewtonsoftJsonSerializer()); + using var graphQLClient = new GraphQLHttpClient("https://swapi.apis.guru/", new NewtonsoftJsonSerializer()); - var personAndFilmsRequest = new GraphQLRequest - { - Query = @" + var personAndFilmsRequest = new GraphQLRequest + { + Query = @" query PersonAndFilms($id: ID) { person(id: $id) { name @@ -26,25 +26,24 @@ query PersonAndFilms($id: ID) { } } }", - OperationName = "PersonAndFilms", - Variables = new - { - id = "cGVvcGxlOjE=" - } - }; + OperationName = "PersonAndFilms", + Variables = new + { + id = "cGVvcGxlOjE=" + } + }; - var graphQLResponse = await graphQLClient.SendQueryAsync(personAndFilmsRequest); - Console.WriteLine("raw response:"); - Console.WriteLine(JsonSerializer.Serialize(graphQLResponse, new JsonSerializerOptions { WriteIndented = true })); + var graphQLResponse = await graphQLClient.SendQueryAsync(personAndFilmsRequest); + Console.WriteLine("raw response:"); + Console.WriteLine(JsonSerializer.Serialize(graphQLResponse, new JsonSerializerOptions { WriteIndented = true })); - Console.WriteLine(); - Console.WriteLine($"Name: {graphQLResponse.Data.Person.Name}"); - var films = string.Join(", ", graphQLResponse.Data.Person.FilmConnection.Films.Select(f => f.Title)); - Console.WriteLine($"Films: {films}"); + Console.WriteLine(); + Console.WriteLine($"Name: {graphQLResponse.Data.Person.Name}"); + var films = string.Join(", ", graphQLResponse.Data.Person.FilmConnection.Films.Select(f => f.Title)); + Console.WriteLine($"Films: {films}"); - Console.WriteLine(); - Console.WriteLine("Press any key to quit..."); - Console.ReadKey(); - } + Console.WriteLine(); + Console.WriteLine("Press any key to quit..."); + Console.ReadKey(); } } diff --git a/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketMessageType.cs b/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketMessageType.cs index 3b0e0499..16125a71 100644 --- a/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketMessageType.cs +++ b/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketMessageType.cs @@ -1,87 +1,86 @@ -namespace GraphQL.Client.Abstractions.Websocket +namespace GraphQL.Client.Abstractions.Websocket; + +public static class GraphQLWebSocketMessageType { - public static class GraphQLWebSocketMessageType - { - /// - /// Client sends this message after plain websocket connection to start the communication with the server - /// The server will response only with GQL_CONNECTION_ACK + GQL_CONNECTION_KEEP_ALIVE(if used) or GQL_CONNECTION_ERROR - /// to this message. - /// payload: Object : optional parameters that the client specifies in connectionParams - /// - public const string GQL_CONNECTION_INIT = "connection_init"; + /// + /// Client sends this message after plain websocket connection to start the communication with the server + /// The server will response only with GQL_CONNECTION_ACK + GQL_CONNECTION_KEEP_ALIVE(if used) or GQL_CONNECTION_ERROR + /// to this message. + /// payload: Object : optional parameters that the client specifies in connectionParams + /// + public const string GQL_CONNECTION_INIT = "connection_init"; - /// - /// The server may responses with this message to the GQL_CONNECTION_INIT from client, indicates the server accepted - /// the connection. - /// - public const string GQL_CONNECTION_ACK = "connection_ack"; // Server -> Client + /// + /// The server may responses with this message to the GQL_CONNECTION_INIT from client, indicates the server accepted + /// the connection. + /// + public const string GQL_CONNECTION_ACK = "connection_ack"; // Server -> Client - /// - /// The server may responses with this message to the GQL_CONNECTION_INIT from client, indicates the server rejected - /// the connection. - /// It server also respond with this message in case of a parsing errors of the message (which does not disconnect the - /// client, just ignore the message). - /// payload: Object: the server side error - /// - public const string GQL_CONNECTION_ERROR = "connection_error"; // Server -> Client + /// + /// The server may responses with this message to the GQL_CONNECTION_INIT from client, indicates the server rejected + /// the connection. + /// It server also respond with this message in case of a parsing errors of the message (which does not disconnect the + /// client, just ignore the message). + /// payload: Object: the server side error + /// + public const string GQL_CONNECTION_ERROR = "connection_error"; // Server -> Client - /// - /// Server message that should be sent right after each GQL_CONNECTION_ACK processed and then periodically to keep the - /// client connection alive. - /// The client starts to consider the keep alive message only upon the first received keep alive message from the - /// server. - /// - /// NOTE: This one here don't follow the standard due to connection optimization - /// - /// - public const string GQL_CONNECTION_KEEP_ALIVE = "ka"; // Server -> Client + /// + /// Server message that should be sent right after each GQL_CONNECTION_ACK processed and then periodically to keep the + /// client connection alive. + /// The client starts to consider the keep alive message only upon the first received keep alive message from the + /// server. + /// + /// NOTE: This one here don't follow the standard due to connection optimization + /// + /// + public const string GQL_CONNECTION_KEEP_ALIVE = "ka"; // Server -> Client - /// - /// Client sends this message to terminate the connection. - /// - public const string GQL_CONNECTION_TERMINATE = "connection_terminate"; // Client -> Server + /// + /// Client sends this message to terminate the connection. + /// + public const string GQL_CONNECTION_TERMINATE = "connection_terminate"; // Client -> Server - /// - /// Client sends this message to execute GraphQL operation - /// id: string : The id of the GraphQL operation to start - /// payload: Object: - /// query: string : GraphQL operation as string or parsed GraphQL document node - /// variables?: Object : Object with GraphQL variables - /// operationName?: string : GraphQL operation name - /// - public const string GQL_START = "start"; + /// + /// Client sends this message to execute GraphQL operation + /// id: string : The id of the GraphQL operation to start + /// payload: Object: + /// query: string : GraphQL operation as string or parsed GraphQL document node + /// variables?: Object : Object with GraphQL variables + /// operationName?: string : GraphQL operation name + /// + public const string GQL_START = "start"; - /// - /// The server sends this message to transfer the GraphQL execution result from the server to the client, this message - /// is a response for GQL_START message. - /// For each GraphQL operation send with GQL_START, the server will respond with at least one GQL_DATA message. - /// id: string : ID of the operation that was successfully set up - /// payload: Object : - /// data: any: Execution result - /// errors?: Error[] : Array of resolvers errors - /// - public const string GQL_DATA = "data"; // Server -> Client + /// + /// The server sends this message to transfer the GraphQL execution result from the server to the client, this message + /// is a response for GQL_START message. + /// For each GraphQL operation send with GQL_START, the server will respond with at least one GQL_DATA message. + /// id: string : ID of the operation that was successfully set up + /// payload: Object : + /// data: any: Execution result + /// errors?: Error[] : Array of resolvers errors + /// + public const string GQL_DATA = "data"; // Server -> Client - /// - /// Server sends this message upon a failing operation, before the GraphQL execution, usually due to GraphQL validation - /// errors (resolver errors are part of GQL_DATA message, and will be added as errors array) - /// payload: Error : payload with the error attributed to the operation failing on the server - /// id: string : operation ID of the operation that failed on the server - /// - public const string GQL_ERROR = "error"; // Server -> Client + /// + /// Server sends this message upon a failing operation, before the GraphQL execution, usually due to GraphQL validation + /// errors (resolver errors are part of GQL_DATA message, and will be added as errors array) + /// payload: Error : payload with the error attributed to the operation failing on the server + /// id: string : operation ID of the operation that failed on the server + /// + public const string GQL_ERROR = "error"; // Server -> Client - /// - /// Server sends this message to indicate that a GraphQL operation is done, and no more data will arrive for the - /// specific operation. - /// id: string : operation ID of the operation that completed - /// - public const string GQL_COMPLETE = "complete"; // Server -> Client + /// + /// Server sends this message to indicate that a GraphQL operation is done, and no more data will arrive for the + /// specific operation. + /// id: string : operation ID of the operation that completed + /// + public const string GQL_COMPLETE = "complete"; // Server -> Client - /// - /// Client sends this message in order to stop a running GraphQL operation execution (for example: unsubscribe) - /// id: string : operation id - /// - public const string GQL_STOP = "stop"; // Client -> Server - } + /// + /// Client sends this message in order to stop a running GraphQL operation execution (for example: unsubscribe) + /// id: string : operation id + /// + public const string GQL_STOP = "stop"; // Client -> Server } diff --git a/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketRequest.cs b/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketRequest.cs index 1210c34a..69c8593e 100644 --- a/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketRequest.cs +++ b/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketRequest.cs @@ -1,108 +1,107 @@ -namespace GraphQL.Client.Abstractions.Websocket +namespace GraphQL.Client.Abstractions.Websocket; + +/// +/// A Subscription Request +/// +public class GraphQLWebSocketRequest : Dictionary, IEquatable { + public const string ID_KEY = "id"; + public const string TYPE_KEY = "type"; + public const string PAYLOAD_KEY = "payload"; + /// - /// A Subscription Request + /// The Identifier of the request /// - public class GraphQLWebSocketRequest : Dictionary, IEquatable + public string Id { - public const string ID_KEY = "id"; - public const string TYPE_KEY = "type"; - public const string PAYLOAD_KEY = "payload"; - - /// - /// The Identifier of the request - /// - public string Id - { - get => TryGetValue(ID_KEY, out object value) ? (string)value : null; - set => this[ID_KEY] = value; - } + get => TryGetValue(ID_KEY, out object value) ? (string)value : null; + set => this[ID_KEY] = value; + } - /// - /// The Type of the Request - /// - public string Type - { - get => TryGetValue(TYPE_KEY, out object value) ? (string)value : null; - set => this[TYPE_KEY] = value; - } + /// + /// The Type of the Request + /// + public string Type + { + get => TryGetValue(TYPE_KEY, out object value) ? (string)value : null; + set => this[TYPE_KEY] = value; + } - /// - /// The payload of the websocket request - /// - public object? Payload - { - get => TryGetValue(PAYLOAD_KEY, out object value) ? value : null; - set => this[PAYLOAD_KEY] = value; - } + /// + /// The payload of the websocket request + /// + public object? Payload + { + get => TryGetValue(PAYLOAD_KEY, out object value) ? value : null; + set => this[PAYLOAD_KEY] = value; + } - private readonly TaskCompletionSource _tcs = new TaskCompletionSource(); + private readonly TaskCompletionSource _tcs = new TaskCompletionSource(); - /// - /// Task used to await the actual send operation and to convey potential exceptions - /// - /// - public Task SendTask() => _tcs.Task; + /// + /// 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 successfully - /// - public void SendCompleted() => _tcs.SetResult(true); + /// + /// gets called when the send operation for this request has completed successfully + /// + 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 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(); + /// + /// 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) => Equals(obj as GraphQLWebSocketRequest); + /// + public override bool Equals(object obj) => Equals(obj as GraphQLWebSocketRequest); - /// - public bool Equals(GraphQLWebSocketRequest other) + /// + public bool Equals(GraphQLWebSocketRequest other) + { + if (other == null) + { + return false; + } + if (ReferenceEquals(this, other)) { - if (other == null) - { - return false; - } - if (ReferenceEquals(this, other)) - { - return true; - } - if (!Equals(Id, other.Id)) - { - return false; - } - if (!Equals(Type, other.Type)) - { - return false; - } - if (!Equals(Payload, other.Payload)) - { - return false; - } return true; } - - /// - public override int GetHashCode() + if (!Equals(Id, other.Id)) { - var hashCode = 9958074; - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Id); - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Type); - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Payload); - return hashCode; + return false; } + if (!Equals(Type, other.Type)) + { + return false; + } + if (!Equals(Payload, other.Payload)) + { + return false; + } + return true; + } - /// - public static bool operator ==(GraphQLWebSocketRequest request1, GraphQLWebSocketRequest request2) => EqualityComparer.Default.Equals(request1, request2); - - /// - public static bool operator !=(GraphQLWebSocketRequest request1, GraphQLWebSocketRequest request2) => !(request1 == request2); + /// + public override int GetHashCode() + { + var hashCode = 9958074; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Id); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Type); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Payload); + return hashCode; } + + /// + public static bool operator ==(GraphQLWebSocketRequest request1, GraphQLWebSocketRequest request2) => EqualityComparer.Default.Equals(request1, request2); + + /// + public static bool operator !=(GraphQLWebSocketRequest request1, GraphQLWebSocketRequest request2) => !(request1 == request2); } diff --git a/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketResponse.cs b/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketResponse.cs index 5de56492..f1241fbc 100644 --- a/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketResponse.cs +++ b/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketResponse.cs @@ -1,97 +1,96 @@ -namespace GraphQL.Client.Abstractions.Websocket +namespace GraphQL.Client.Abstractions.Websocket; + +/// +/// A Subscription Response +/// +public class GraphQLWebSocketResponse : IEquatable { /// - /// A Subscription Response + /// The Identifier of the Response /// - public class GraphQLWebSocketResponse : IEquatable - { - /// - /// The Identifier of the Response - /// - public string Id { get; set; } + public string Id { get; set; } - /// - /// The Type of the Response - /// - public string Type { get; set; } + /// + /// The Type of the Response + /// + public string Type { get; set; } - /// - public override bool Equals(object obj) => Equals(obj as GraphQLWebSocketResponse); + /// + public override bool Equals(object obj) => Equals(obj as GraphQLWebSocketResponse); - /// - public bool Equals(GraphQLWebSocketResponse other) + /// + public bool Equals(GraphQLWebSocketResponse other) + { + if (other == null) { - if (other == null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (!Equals(Id, other.Id)) - { - return false; - } - - if (!Equals(Type, other.Type)) - { - return false; - } + return false; + } + if (ReferenceEquals(this, other)) + { return true; } - /// - public override int GetHashCode() + if (!Equals(Id, other.Id)) { - var hashCode = 9958074; - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Id); - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Type); - return hashCode; + return false; } - /// - public static bool operator ==(GraphQLWebSocketResponse response1, GraphQLWebSocketResponse response2) => - EqualityComparer.Default.Equals(response1, response2); + if (!Equals(Type, other.Type)) + { + return false; + } - /// - public static bool operator !=(GraphQLWebSocketResponse response1, GraphQLWebSocketResponse response2) => - !(response1 == response2); + return true; } - public class GraphQLWebSocketResponse : GraphQLWebSocketResponse, IEquatable> + /// + public override int GetHashCode() { - public TPayload Payload { get; set; } + var hashCode = 9958074; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Id); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Type); + return hashCode; + } - public bool Equals(GraphQLWebSocketResponse? other) - { - if (other is null) - return false; - if (ReferenceEquals(this, other)) - return true; - return base.Equals(other) && Payload.Equals(other.Payload); - } + /// + public static bool operator ==(GraphQLWebSocketResponse response1, GraphQLWebSocketResponse response2) => + EqualityComparer.Default.Equals(response1, response2); - public override bool Equals(object? obj) - { - if (obj is null) - return false; - if (ReferenceEquals(this, obj)) - return true; - if (obj.GetType() != GetType()) - return false; - return Equals((GraphQLWebSocketResponse)obj); - } + /// + public static bool operator !=(GraphQLWebSocketResponse response1, GraphQLWebSocketResponse response2) => + !(response1 == response2); +} + +public class GraphQLWebSocketResponse : GraphQLWebSocketResponse, IEquatable> +{ + public TPayload Payload { get; set; } + + public bool Equals(GraphQLWebSocketResponse? other) + { + if (other is null) + return false; + if (ReferenceEquals(this, other)) + return true; + return base.Equals(other) && Payload.Equals(other.Payload); + } - public override int GetHashCode() + public override bool Equals(object? obj) + { + if (obj is null) + return false; + if (ReferenceEquals(this, obj)) + return true; + if (obj.GetType() != GetType()) + return false; + return Equals((GraphQLWebSocketResponse)obj); + } + + public override int GetHashCode() + { + unchecked { - unchecked - { - return (base.GetHashCode() * 397) ^ Payload.GetHashCode(); - } + return (base.GetHashCode() * 397) ^ Payload.GetHashCode(); } } } diff --git a/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebsocketConnectionState.cs b/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebsocketConnectionState.cs index c5fd3107..e12957a1 100644 --- a/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebsocketConnectionState.cs +++ b/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebsocketConnectionState.cs @@ -1,11 +1,10 @@ -namespace GraphQL.Client.Abstractions.Websocket +namespace GraphQL.Client.Abstractions.Websocket; + +public enum GraphQLWebsocketConnectionState { - public enum GraphQLWebsocketConnectionState - { - Disconnected, + Disconnected, - Connecting, + Connecting, - Connected - } + Connected } diff --git a/src/GraphQL.Client.Abstractions.Websocket/IGraphQLWebsocketJsonSerializer.cs b/src/GraphQL.Client.Abstractions.Websocket/IGraphQLWebsocketJsonSerializer.cs index 4526d797..f7c4a2bf 100644 --- a/src/GraphQL.Client.Abstractions.Websocket/IGraphQLWebsocketJsonSerializer.cs +++ b/src/GraphQL.Client.Abstractions.Websocket/IGraphQLWebsocketJsonSerializer.cs @@ -1,15 +1,14 @@ -namespace GraphQL.Client.Abstractions.Websocket +namespace GraphQL.Client.Abstractions.Websocket; + +/// +/// The json serializer interface for the graphql-dotnet http client. +/// Implementations should provide a parameterless constructor for convenient usage +/// +public interface IGraphQLWebsocketJsonSerializer : IGraphQLJsonSerializer { - /// - /// The json serializer interface for the graphql-dotnet http client. - /// Implementations should provide a parameterless constructor for convenient usage - /// - public interface IGraphQLWebsocketJsonSerializer : IGraphQLJsonSerializer - { - byte[] SerializeToBytes(GraphQLWebSocketRequest request); + byte[] SerializeToBytes(GraphQLWebSocketRequest request); - Task DeserializeToWebsocketResponseWrapperAsync(Stream stream); + Task DeserializeToWebsocketResponseWrapperAsync(Stream stream); - GraphQLWebSocketResponse> DeserializeToWebsocketResponse(byte[] bytes); - } + GraphQLWebSocketResponse> DeserializeToWebsocketResponse(byte[] bytes); } diff --git a/src/GraphQL.Client.Abstractions.Websocket/WebsocketMessageWrapper.cs b/src/GraphQL.Client.Abstractions.Websocket/WebsocketMessageWrapper.cs index e7ca0ab8..a94cb8a6 100644 --- a/src/GraphQL.Client.Abstractions.Websocket/WebsocketMessageWrapper.cs +++ b/src/GraphQL.Client.Abstractions.Websocket/WebsocketMessageWrapper.cs @@ -1,11 +1,10 @@ using System.Runtime.Serialization; -namespace GraphQL.Client.Abstractions.Websocket +namespace GraphQL.Client.Abstractions.Websocket; + +public class WebsocketMessageWrapper : GraphQLWebSocketResponse { - public class WebsocketMessageWrapper : GraphQLWebSocketResponse - { - [IgnoreDataMember] - public byte[] MessageBytes { get; set; } - } + [IgnoreDataMember] + public byte[] MessageBytes { get; set; } } diff --git a/src/GraphQL.Client.Abstractions/GraphQLClientExtensions.cs b/src/GraphQL.Client.Abstractions/GraphQLClientExtensions.cs index 379eb932..e27d1a9a 100644 --- a/src/GraphQL.Client.Abstractions/GraphQLClientExtensions.cs +++ b/src/GraphQL.Client.Abstractions/GraphQLClientExtensions.cs @@ -1,51 +1,50 @@ -namespace GraphQL.Client.Abstractions +namespace GraphQL.Client.Abstractions; + +public static class GraphQLClientExtensions { - public static class GraphQLClientExtensions + public static Task> SendQueryAsync(this IGraphQLClient client, + string query, object? variables = null, + string? operationName = null, Func defineResponseType = null, CancellationToken cancellationToken = default) { - public static Task> SendQueryAsync(this IGraphQLClient client, - string query, object? variables = null, - string? operationName = null, Func defineResponseType = null, CancellationToken cancellationToken = default) - { - _ = defineResponseType; - return client.SendQueryAsync(new GraphQLRequest(query, variables, operationName), - cancellationToken: cancellationToken); - } + _ = defineResponseType; + return client.SendQueryAsync(new GraphQLRequest(query, variables, operationName), + cancellationToken: cancellationToken); + } - public static Task> SendMutationAsync(this IGraphQLClient client, - string query, object? variables = null, - string? operationName = null, Func defineResponseType = null, CancellationToken cancellationToken = default) - { - _ = defineResponseType; - return client.SendMutationAsync(new GraphQLRequest(query, variables, operationName), - cancellationToken: cancellationToken); - } + public static Task> SendMutationAsync(this IGraphQLClient client, + string query, object? variables = null, + string? operationName = null, Func defineResponseType = null, CancellationToken cancellationToken = default) + { + _ = defineResponseType; + return client.SendMutationAsync(new GraphQLRequest(query, variables, operationName), + cancellationToken: cancellationToken); + } - public static Task> SendQueryAsync(this IGraphQLClient client, - GraphQLRequest request, Func defineResponseType, CancellationToken cancellationToken = default) - { - _ = defineResponseType; - return client.SendQueryAsync(request, cancellationToken); - } + public static Task> SendQueryAsync(this IGraphQLClient client, + GraphQLRequest request, Func defineResponseType, CancellationToken cancellationToken = default) + { + _ = defineResponseType; + return client.SendQueryAsync(request, cancellationToken); + } - public static Task> SendMutationAsync(this IGraphQLClient client, - GraphQLRequest request, Func defineResponseType, CancellationToken cancellationToken = default) - { - _ = defineResponseType; - return client.SendMutationAsync(request, cancellationToken); - } + public static Task> SendMutationAsync(this IGraphQLClient client, + GraphQLRequest request, Func defineResponseType, CancellationToken cancellationToken = default) + { + _ = defineResponseType; + return client.SendMutationAsync(request, cancellationToken); + } - public static IObservable> CreateSubscriptionStream( - this IGraphQLClient client, GraphQLRequest request, Func defineResponseType) - { - _ = defineResponseType; - return client.CreateSubscriptionStream(request); - } + public static IObservable> CreateSubscriptionStream( + this IGraphQLClient client, GraphQLRequest request, Func defineResponseType) + { + _ = defineResponseType; + return client.CreateSubscriptionStream(request); + } - public static IObservable> CreateSubscriptionStream( - this IGraphQLClient client, GraphQLRequest request, Func defineResponseType, Action exceptionHandler) - { - _ = defineResponseType; - return client.CreateSubscriptionStream(request, exceptionHandler); - } + public static IObservable> CreateSubscriptionStream( + this IGraphQLClient client, GraphQLRequest request, Func defineResponseType, Action exceptionHandler) + { + _ = defineResponseType; + return client.CreateSubscriptionStream(request, exceptionHandler); } } diff --git a/src/GraphQL.Client.Abstractions/GraphQLJsonSerializerExtensions.cs b/src/GraphQL.Client.Abstractions/GraphQLJsonSerializerExtensions.cs index 56ff68e6..248f594a 100644 --- a/src/GraphQL.Client.Abstractions/GraphQLJsonSerializerExtensions.cs +++ b/src/GraphQL.Client.Abstractions/GraphQLJsonSerializerExtensions.cs @@ -1,14 +1,13 @@ -namespace GraphQL.Client.Abstractions +namespace GraphQL.Client.Abstractions; + +public static class GraphQLJsonSerializerExtensions { - public static class GraphQLJsonSerializerExtensions - { - public static TOptions New(this Action configure) => - configure.AndReturn(Activator.CreateInstance()); + public static TOptions New(this Action configure) => + configure.AndReturn(Activator.CreateInstance()); - public static TOptions AndReturn(this Action configure, TOptions options) - { - configure(options); - return options; - } + public static TOptions AndReturn(this Action configure, TOptions options) + { + configure(options); + return options; } } diff --git a/src/GraphQL.Client.Abstractions/IGraphQLClient.cs b/src/GraphQL.Client.Abstractions/IGraphQLClient.cs index f289786b..e868a20e 100644 --- a/src/GraphQL.Client.Abstractions/IGraphQLClient.cs +++ b/src/GraphQL.Client.Abstractions/IGraphQLClient.cs @@ -1,32 +1,31 @@ using System.Net.WebSockets; -namespace GraphQL.Client.Abstractions +namespace GraphQL.Client.Abstractions; + +public interface IGraphQLClient { - public interface IGraphQLClient - { - Task> SendQueryAsync(GraphQLRequest request, CancellationToken cancellationToken = default); + Task> SendQueryAsync(GraphQLRequest request, CancellationToken cancellationToken = default); - Task> SendMutationAsync(GraphQLRequest request, CancellationToken cancellationToken = default); + Task> SendMutationAsync(GraphQLRequest request, CancellationToken cancellationToken = default); - /// - /// Creates a subscription to a GraphQL server. The connection is not established until the first actual subscription is made.
- /// All subscriptions made to this stream share the same hot observable.
- /// The stream must be recreated completely after an error has occurred within its logic (i.e. a ) - ///
- /// the GraphQL request for this subscription - /// an observable stream for the specified subscription - IObservable> CreateSubscriptionStream(GraphQLRequest request); + /// + /// Creates a subscription to a GraphQL server. The connection is not established until the first actual subscription is made.
+ /// All subscriptions made to this stream share the same hot observable.
+ /// The stream must be recreated completely after an error has occurred within its logic (i.e. a ) + ///
+ /// the GraphQL request for this subscription + /// an observable stream for the specified subscription + IObservable> CreateSubscriptionStream(GraphQLRequest request); - /// - /// Creates a subscription to a GraphQL server. The connection is not established until the first actual subscription is made.
- /// All subscriptions made to this stream share the same hot observable.
- /// All s are passed to the to be handled externally.
- /// If the completes normally, the subscription is recreated with a new connection attempt.
- /// Any exception thrown by will cause the sequence to fail. - ///
- /// the GraphQL request for this subscription - /// an external handler for all s occurring within the sequence - /// an observable stream for the specified subscription - IObservable> CreateSubscriptionStream(GraphQLRequest request, Action exceptionHandler); - } + /// + /// Creates a subscription to a GraphQL server. The connection is not established until the first actual subscription is made.
+ /// All subscriptions made to this stream share the same hot observable.
+ /// All s are passed to the to be handled externally.
+ /// If the completes normally, the subscription is recreated with a new connection attempt.
+ /// Any exception thrown by will cause the sequence to fail. + ///
+ /// the GraphQL request for this subscription + /// an external handler for all s occurring within the sequence + /// an observable stream for the specified subscription + IObservable> CreateSubscriptionStream(GraphQLRequest request, Action exceptionHandler); } diff --git a/src/GraphQL.Client.Abstractions/IGraphQLJsonSerializer.cs b/src/GraphQL.Client.Abstractions/IGraphQLJsonSerializer.cs index 9f5f9410..28690947 100644 --- a/src/GraphQL.Client.Abstractions/IGraphQLJsonSerializer.cs +++ b/src/GraphQL.Client.Abstractions/IGraphQLJsonSerializer.cs @@ -1,9 +1,8 @@ -namespace GraphQL.Client.Abstractions +namespace GraphQL.Client.Abstractions; + +public interface IGraphQLJsonSerializer { - public interface IGraphQLJsonSerializer - { - string SerializeToString(GraphQLRequest request); + string SerializeToString(GraphQLRequest request); - Task> DeserializeFromUtf8StreamAsync(Stream stream, CancellationToken cancellationToken); - } + Task> DeserializeFromUtf8StreamAsync(Stream stream, CancellationToken cancellationToken); } diff --git a/src/GraphQL.Client.Abstractions/Utilities/StringExtensions.cs b/src/GraphQL.Client.Abstractions/Utilities/StringExtensions.cs index 8aeb4cb0..f751adb8 100644 --- a/src/GraphQL.Client.Abstractions/Utilities/StringExtensions.cs +++ b/src/GraphQL.Client.Abstractions/Utilities/StringExtensions.cs @@ -1,35 +1,34 @@ -namespace GraphQL.Client.Abstractions.Utilities +namespace GraphQL.Client.Abstractions.Utilities; + +/// +/// Copied from https://github.com/jquense/StringUtils +/// +public static class StringExtensions { - /// - /// Copied from https://github.com/jquense/StringUtils - /// - public static class StringExtensions - { - public static string StripIndent(this string str) => GraphQL.Client.Abstractions.Utilities.StringUtils.StripIndent(str); + public static string StripIndent(this string str) => GraphQL.Client.Abstractions.Utilities.StringUtils.StripIndent(str); - public static IEnumerable ToWords(this string str) => GraphQL.Client.Abstractions.Utilities.StringUtils.ToWords(str); + public static IEnumerable ToWords(this string str) => GraphQL.Client.Abstractions.Utilities.StringUtils.ToWords(str); - public static string ToUpperFirst(this string str) => GraphQL.Client.Abstractions.Utilities.StringUtils.ToUpperFirst(str); + public static string ToUpperFirst(this string str) => GraphQL.Client.Abstractions.Utilities.StringUtils.ToUpperFirst(str); - public static string ToLowerFirst(this string str) => GraphQL.Client.Abstractions.Utilities.StringUtils.ToLowerFirst(str); + public static string ToLowerFirst(this string str) => GraphQL.Client.Abstractions.Utilities.StringUtils.ToLowerFirst(str); - public static string Capitalize(this string str) => GraphQL.Client.Abstractions.Utilities.StringUtils.Capitalize(str); + public static string Capitalize(this string str) => GraphQL.Client.Abstractions.Utilities.StringUtils.Capitalize(str); - public static string ToCamelCase(this string str) => GraphQL.Client.Abstractions.Utilities.StringUtils.ToCamelCase(str); + public static string ToCamelCase(this string str) => GraphQL.Client.Abstractions.Utilities.StringUtils.ToCamelCase(str); - public static string ToConstantCase(this string str) => GraphQL.Client.Abstractions.Utilities.StringUtils.ToConstantCase(str); + public static string ToConstantCase(this string str) => GraphQL.Client.Abstractions.Utilities.StringUtils.ToConstantCase(str); - public static string ToUpperCase(this string str) => GraphQL.Client.Abstractions.Utilities.StringUtils.ToUpperCase(str); + public static string ToUpperCase(this string str) => GraphQL.Client.Abstractions.Utilities.StringUtils.ToUpperCase(str); - public static string ToLowerCase(this string str) => GraphQL.Client.Abstractions.Utilities.StringUtils.ToLowerCase(str); + public static string ToLowerCase(this string str) => GraphQL.Client.Abstractions.Utilities.StringUtils.ToLowerCase(str); - public static string ToPascalCase(this string str) => GraphQL.Client.Abstractions.Utilities.StringUtils.ToPascalCase(str); + public static string ToPascalCase(this string str) => GraphQL.Client.Abstractions.Utilities.StringUtils.ToPascalCase(str); - public static string ToKebabCase(this string str) => GraphQL.Client.Abstractions.Utilities.StringUtils.ToKebabCase(str); + public static string ToKebabCase(this string str) => GraphQL.Client.Abstractions.Utilities.StringUtils.ToKebabCase(str); - public static string ToSnakeCase(this string str) => GraphQL.Client.Abstractions.Utilities.StringUtils.ToSnakeCase(str); - } + public static string ToSnakeCase(this string str) => GraphQL.Client.Abstractions.Utilities.StringUtils.ToSnakeCase(str); } diff --git a/src/GraphQL.Client.Abstractions/Utilities/StringUtils.cs b/src/GraphQL.Client.Abstractions/Utilities/StringUtils.cs index 57673216..27fbf992 100644 --- a/src/GraphQL.Client.Abstractions/Utilities/StringUtils.cs +++ b/src/GraphQL.Client.Abstractions/Utilities/StringUtils.cs @@ -1,190 +1,189 @@ using System.Text.RegularExpressions; -namespace GraphQL.Client.Abstractions.Utilities +namespace GraphQL.Client.Abstractions.Utilities; + +/// +/// Copied from https://github.com/jquense/StringUtils +/// +public static class StringUtils { + private static readonly Regex _reWords = new Regex(@"[A-Z\xc0-\xd6\xd8-\xde]?[a-z\xdf-\xf6\xf8-\xff]+(?:['’](?:d|ll|m|re|s|t|ve))?(?=[\xac\xb1\xd7\xf7\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xbf\u2000-\u206f \t\x0b\f\xa0\ufeff\n\r\u2028\u2029\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000]|[A-Z\xc0-\xd6\xd8-\xde]|$)|(?:[A-Z\xc0-\xd6\xd8-\xde]|[^\ud800-\udfff\xac\xb1\xd7\xf7\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xbf\u2000-\u206f \t\x0b\f\xa0\ufeff\n\r\u2028\u2029\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\d+\u2700-\u27bfa-z\xdf-\xf6\xf8-\xffA-Z\xc0-\xd6\xd8-\xde])+(?:['’](?:D|LL|M|RE|S|T|VE))?(?=[\xac\xb1\xd7\xf7\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xbf\u2000-\u206f \t\x0b\f\xa0\ufeff\n\r\u2028\u2029\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000]|[A-Z\xc0-\xd6\xd8-\xde](?:[a-z\xdf-\xf6\xf8-\xff]|[^\ud800-\udfff\xac\xb1\xd7\xf7\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xbf\u2000-\u206f \t\x0b\f\xa0\ufeff\n\r\u2028\u2029\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\d+\u2700-\u27bfa-z\xdf-\xf6\xf8-\xffA-Z\xc0-\xd6\xd8-\xde])|$)|[A-Z\xc0-\xd6\xd8-\xde]?(?:[a-z\xdf-\xf6\xf8-\xff]|[^\ud800-\udfff\xac\xb1\xd7\xf7\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xbf\u2000-\u206f \t\x0b\f\xa0\ufeff\n\r\u2028\u2029\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\d+\u2700-\u27bfa-z\xdf-\xf6\xf8-\xffA-Z\xc0-\xd6\xd8-\xde])+(?:['’](?:d|ll|m|re|s|t|ve))?|[A-Z\xc0-\xd6\xd8-\xde]+(?:['’](?:D|LL|M|RE|S|T|VE))?|\d+|(?:[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff])[\ufe0e\ufe0f]?(?:[\u0300-\u036f\ufe20-\ufe23\u20d0-\u20f0]|\ud83c[\udffb-\udfff])?(?:\u200d(?:[^\ud800-\udfff]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff])[\ufe0e\ufe0f]?(?:[\u0300-\u036f\ufe20-\ufe23\u20d0-\u20f0]|\ud83c[\udffb-\udfff])?)*"); + private static readonly Regex _reIndent = new Regex(@"^[ \t]*(?=\S)", RegexOptions.Multiline); + /// - /// Copied from https://github.com/jquense/StringUtils + /// Removes the leading indent from a multi-line string /// - public static class StringUtils + /// String + /// + public static string StripIndent(string str) { - private static readonly Regex _reWords = new Regex(@"[A-Z\xc0-\xd6\xd8-\xde]?[a-z\xdf-\xf6\xf8-\xff]+(?:['’](?:d|ll|m|re|s|t|ve))?(?=[\xac\xb1\xd7\xf7\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xbf\u2000-\u206f \t\x0b\f\xa0\ufeff\n\r\u2028\u2029\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000]|[A-Z\xc0-\xd6\xd8-\xde]|$)|(?:[A-Z\xc0-\xd6\xd8-\xde]|[^\ud800-\udfff\xac\xb1\xd7\xf7\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xbf\u2000-\u206f \t\x0b\f\xa0\ufeff\n\r\u2028\u2029\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\d+\u2700-\u27bfa-z\xdf-\xf6\xf8-\xffA-Z\xc0-\xd6\xd8-\xde])+(?:['’](?:D|LL|M|RE|S|T|VE))?(?=[\xac\xb1\xd7\xf7\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xbf\u2000-\u206f \t\x0b\f\xa0\ufeff\n\r\u2028\u2029\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000]|[A-Z\xc0-\xd6\xd8-\xde](?:[a-z\xdf-\xf6\xf8-\xff]|[^\ud800-\udfff\xac\xb1\xd7\xf7\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xbf\u2000-\u206f \t\x0b\f\xa0\ufeff\n\r\u2028\u2029\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\d+\u2700-\u27bfa-z\xdf-\xf6\xf8-\xffA-Z\xc0-\xd6\xd8-\xde])|$)|[A-Z\xc0-\xd6\xd8-\xde]?(?:[a-z\xdf-\xf6\xf8-\xff]|[^\ud800-\udfff\xac\xb1\xd7\xf7\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xbf\u2000-\u206f \t\x0b\f\xa0\ufeff\n\r\u2028\u2029\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\d+\u2700-\u27bfa-z\xdf-\xf6\xf8-\xffA-Z\xc0-\xd6\xd8-\xde])+(?:['’](?:d|ll|m|re|s|t|ve))?|[A-Z\xc0-\xd6\xd8-\xde]+(?:['’](?:D|LL|M|RE|S|T|VE))?|\d+|(?:[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff])[\ufe0e\ufe0f]?(?:[\u0300-\u036f\ufe20-\ufe23\u20d0-\u20f0]|\ud83c[\udffb-\udfff])?(?:\u200d(?:[^\ud800-\udfff]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff])[\ufe0e\ufe0f]?(?:[\u0300-\u036f\ufe20-\ufe23\u20d0-\u20f0]|\ud83c[\udffb-\udfff])?)*"); - private static readonly Regex _reIndent = new Regex(@"^[ \t]*(?=\S)", RegexOptions.Multiline); - - /// - /// Removes the leading indent from a multi-line string - /// - /// String - /// - public static string StripIndent(string str) - { - int indent = _reIndent.Matches(str).Cast().Select(m => m.Value.Length).Min(); - return new Regex(@"^[ \t]{" + indent + "}", RegexOptions.Multiline).Replace(str, ""); - } + int indent = _reIndent.Matches(str).Cast().Select(m => m.Value.Length).Min(); + return new Regex(@"^[ \t]{" + indent + "}", RegexOptions.Multiline).Replace(str, ""); + } - /// - /// Split a cased string into a series of "words" excluding the seperator. - /// - /// - /// - public static IEnumerable ToWords(string str) + /// + /// Split a cased string into a series of "words" excluding the seperator. + /// + /// + /// + public static IEnumerable ToWords(string str) + { + foreach (Match match in _reWords.Matches(str)) { - foreach (Match match in _reWords.Matches(str)) - { - yield return match.Value; - } + yield return match.Value; } + } - /// - /// Uppercase the first character in a string, leaving the rest of the string as is - /// - /// - /// a string with the first character uppercased - public static string ToUpperFirst(string str) => ChangeCaseFirst(str, c => c.ToUpperInvariant()); - - /// - /// Lowercase the first character in a string, leaving the rest of the string as is - /// - /// - /// a string with the first character lowercased - public static string ToLowerFirst(string str) => ChangeCaseFirst(str, c => c.ToLowerInvariant()); - - /// - /// Capitalizes a string, lowercasing the entire string and uppercasing the first character - /// - /// - /// a capitalized string - public static string Capitalize(string str) => ToUpperFirst(str.ToLowerInvariant()); - - /// - /// Converts a string to camelCase. - /// - /// - /// StringUtils.ToCamelCase("FOOBAR") // "foobar" - /// StringUtils.ToCamelCase("FOO_BAR") // "fooBar" - /// StringUtils.ToCamelCase("FooBar") // "fooBar" - /// StringUtils.ToCamelCase("foo bar") // "fooBar" - /// - /// - /// - public static string ToCamelCase(string str) => - ChangeCase(str, (word, index) => - (index == 0 ? word.ToLowerInvariant() : Capitalize(word))); - - /// - /// Convert a string to CONSTANT_CASE - /// - /// - /// StringUtils.ToConstantCase("fOo BaR") // "FOO_BAR" - /// StringUtils.ToConstantCase("FooBar") // "FOO_BAR" - /// StringUtils.ToConstantCase("Foo Bar") // "FOO_BAR" - /// - /// - /// - public static string ToConstantCase(string str) => ChangeCase(str, "_", w => w.ToUpperInvariant()); - - /// - /// Convert a string to UPPERCASE - /// - /// - /// StringUtils.ToUpperCase("foobar") // "FOOBAR" - /// StringUtils.ToUpperCase("FOO_BAR") // "FOO BAR" - /// StringUtils.ToUpperCase("FooBar") // "FOO BAR" - /// StringUtils.ToUpperCase("Foo Bar") // "FOO BAR" - /// - /// - /// - public static string ToUpperCase(string str) => ChangeCase(str, " ", (word) => word.ToUpperInvariant()); - - /// - /// Convert a string to lowercase - /// - /// - /// StringUtils.ToLowerCase("FOOBAR") // "foobar" - /// StringUtils.ToLowerCase("FOO_BAR") // "foo bar" - /// StringUtils.ToLowerCase("FooBar") // "foo bar" - /// StringUtils.ToLowerCase("Foo Bar") // "foo bar" - /// - /// - /// - public static string ToLowerCase(string str) => ChangeCase(str, " ", word => word.ToLowerInvariant()); - - /// - /// convert a string to PascalCase - /// - /// - /// StringUtils.ToPascalCase("FOOBAR") // "FooBar" - /// StringUtils.ToPascalCase("FOO_BAR") // "FooBar" - /// StringUtils.ToPascalCase("fooBar") // "FooBar" - /// StringUtils.ToPascalCase("Foo Bar") // "FooBar" - /// - /// - /// - public static string ToPascalCase(string str) => ChangeCase(str, Capitalize); - - /// - /// convert a string to kebab-case - /// - /// - /// StringUtils.ToKebabCase("FOOBAR") // "foo-bar" - /// StringUtils.ToKebabCase("FOO_BAR") // "foo-bar" - /// StringUtils.ToKebabCase("fooBar") // "foo-bar" - /// StringUtils.ToKebabCase("Foo Bar") // "foo-bar" - /// - /// - /// - public static string ToKebabCase(string str) => ChangeCase(str, "-", word => word.ToLowerInvariant()); - - /// - /// convert a string to snake_case - /// - /// - /// StringUtils.ToSnakeCase("FOOBAR") // "foo_bar" - /// StringUtils.ToSnakeCase("FOO_BAR") // "foo_bar" - /// StringUtils.ToSnakeCase("fooBar") // "foo_bar" - /// StringUtils.ToSnakeCase("Foo Bar") // "foo_bar" - /// - /// - /// - public static string ToSnakeCase(string str) => ChangeCase(str, "_", word => word.ToLowerInvariant()); - - public static string ChangeCase(string str, Func composer) => ChangeCase(str, "", composer); - - public static string ChangeCase(string str, string sep, Func composer) => ChangeCase(str, sep, (w, i) => composer(w)); - - public static string ChangeCase(string str, Func composer) => ChangeCase(str, "", composer); - - /// - /// Convert a string to a new case - /// - /// - /// Convert a string to inverse camelCase: CAMELcASE - /// - /// StringUtils.ChangeCase("my string", "", (word, index) => { - /// word = word.ToUpperInvariant(); - /// if (index > 0) - /// word = StringUtils.toLowerFirst(word); - /// return word - /// }); - /// // "MYsTRING" - /// - /// - /// an input string - /// a seperator string used between "words" in the string - /// a function that converts individual words to a new case - /// - public static string ChangeCase(string str, string sep, Func composer) - { - string result = ""; - int index = 0; + /// + /// Uppercase the first character in a string, leaving the rest of the string as is + /// + /// + /// a string with the first character uppercased + public static string ToUpperFirst(string str) => ChangeCaseFirst(str, c => c.ToUpperInvariant()); - foreach (string word in ToWords(str)) - { - result += ((index == 0 ? "" : sep) + composer(word, index++)); - } + /// + /// Lowercase the first character in a string, leaving the rest of the string as is + /// + /// + /// a string with the first character lowercased + public static string ToLowerFirst(string str) => ChangeCaseFirst(str, c => c.ToLowerInvariant()); + + /// + /// Capitalizes a string, lowercasing the entire string and uppercasing the first character + /// + /// + /// a capitalized string + public static string Capitalize(string str) => ToUpperFirst(str.ToLowerInvariant()); + + /// + /// Converts a string to camelCase. + /// + /// + /// StringUtils.ToCamelCase("FOOBAR") // "foobar" + /// StringUtils.ToCamelCase("FOO_BAR") // "fooBar" + /// StringUtils.ToCamelCase("FooBar") // "fooBar" + /// StringUtils.ToCamelCase("foo bar") // "fooBar" + /// + /// + /// + public static string ToCamelCase(string str) => + ChangeCase(str, (word, index) => + (index == 0 ? word.ToLowerInvariant() : Capitalize(word))); + + /// + /// Convert a string to CONSTANT_CASE + /// + /// + /// StringUtils.ToConstantCase("fOo BaR") // "FOO_BAR" + /// StringUtils.ToConstantCase("FooBar") // "FOO_BAR" + /// StringUtils.ToConstantCase("Foo Bar") // "FOO_BAR" + /// + /// + /// + public static string ToConstantCase(string str) => ChangeCase(str, "_", w => w.ToUpperInvariant()); - return result; + /// + /// Convert a string to UPPERCASE + /// + /// + /// StringUtils.ToUpperCase("foobar") // "FOOBAR" + /// StringUtils.ToUpperCase("FOO_BAR") // "FOO BAR" + /// StringUtils.ToUpperCase("FooBar") // "FOO BAR" + /// StringUtils.ToUpperCase("Foo Bar") // "FOO BAR" + /// + /// + /// + public static string ToUpperCase(string str) => ChangeCase(str, " ", (word) => word.ToUpperInvariant()); + + /// + /// Convert a string to lowercase + /// + /// + /// StringUtils.ToLowerCase("FOOBAR") // "foobar" + /// StringUtils.ToLowerCase("FOO_BAR") // "foo bar" + /// StringUtils.ToLowerCase("FooBar") // "foo bar" + /// StringUtils.ToLowerCase("Foo Bar") // "foo bar" + /// + /// + /// + public static string ToLowerCase(string str) => ChangeCase(str, " ", word => word.ToLowerInvariant()); + + /// + /// convert a string to PascalCase + /// + /// + /// StringUtils.ToPascalCase("FOOBAR") // "FooBar" + /// StringUtils.ToPascalCase("FOO_BAR") // "FooBar" + /// StringUtils.ToPascalCase("fooBar") // "FooBar" + /// StringUtils.ToPascalCase("Foo Bar") // "FooBar" + /// + /// + /// + public static string ToPascalCase(string str) => ChangeCase(str, Capitalize); + + /// + /// convert a string to kebab-case + /// + /// + /// StringUtils.ToKebabCase("FOOBAR") // "foo-bar" + /// StringUtils.ToKebabCase("FOO_BAR") // "foo-bar" + /// StringUtils.ToKebabCase("fooBar") // "foo-bar" + /// StringUtils.ToKebabCase("Foo Bar") // "foo-bar" + /// + /// + /// + public static string ToKebabCase(string str) => ChangeCase(str, "-", word => word.ToLowerInvariant()); + + /// + /// convert a string to snake_case + /// + /// + /// StringUtils.ToSnakeCase("FOOBAR") // "foo_bar" + /// StringUtils.ToSnakeCase("FOO_BAR") // "foo_bar" + /// StringUtils.ToSnakeCase("fooBar") // "foo_bar" + /// StringUtils.ToSnakeCase("Foo Bar") // "foo_bar" + /// + /// + /// + public static string ToSnakeCase(string str) => ChangeCase(str, "_", word => word.ToLowerInvariant()); + + public static string ChangeCase(string str, Func composer) => ChangeCase(str, "", composer); + + public static string ChangeCase(string str, string sep, Func composer) => ChangeCase(str, sep, (w, i) => composer(w)); + + public static string ChangeCase(string str, Func composer) => ChangeCase(str, "", composer); + + /// + /// Convert a string to a new case + /// + /// + /// Convert a string to inverse camelCase: CAMELcASE + /// + /// StringUtils.ChangeCase("my string", "", (word, index) => { + /// word = word.ToUpperInvariant(); + /// if (index > 0) + /// word = StringUtils.toLowerFirst(word); + /// return word + /// }); + /// // "MYsTRING" + /// + /// + /// an input string + /// a seperator string used between "words" in the string + /// a function that converts individual words to a new case + /// + public static string ChangeCase(string str, string sep, Func composer) + { + string result = ""; + int index = 0; + + foreach (string word in ToWords(str)) + { + result += ((index == 0 ? "" : sep) + composer(word, index++)); } - private static string ChangeCaseFirst(string str, Func change) => change(str.Substring(0, 1)) + str.Substring(1); + return result; } + + private static string ChangeCaseFirst(string str, Func change) => change(str.Substring(0, 1)) + str.Substring(1); } diff --git a/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs b/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs index 4a92e647..15e4c3e3 100644 --- a/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs +++ b/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs @@ -3,96 +3,95 @@ using GraphQL.Client.Abstractions; using GraphQL.Types; -namespace GraphQL.Client.LocalExecution +namespace GraphQL.Client.LocalExecution; + +public static class GraphQLLocalExecutionClient { - public static class GraphQLLocalExecutionClient - { - public static GraphQLLocalExecutionClient New(TSchema schema, IGraphQLJsonSerializer clientSerializer, IGraphQLTextSerializer serverSerializer) - where TSchema : ISchema - => new GraphQLLocalExecutionClient(schema, new DocumentExecuter(), clientSerializer, serverSerializer); - } + public static GraphQLLocalExecutionClient New(TSchema schema, IGraphQLJsonSerializer clientSerializer, IGraphQLTextSerializer serverSerializer) + where TSchema : ISchema + => new GraphQLLocalExecutionClient(schema, new DocumentExecuter(), clientSerializer, serverSerializer); +} - public class GraphQLLocalExecutionClient : IGraphQLClient where TSchema : ISchema - { - public TSchema Schema { get; } +public class GraphQLLocalExecutionClient : IGraphQLClient where TSchema : ISchema +{ + public TSchema Schema { get; } - public IGraphQLJsonSerializer Serializer { get; } + public IGraphQLJsonSerializer Serializer { get; } - private readonly IDocumentExecuter _documentExecuter; - private readonly IGraphQLTextSerializer _documentSerializer; + private readonly IDocumentExecuter _documentExecuter; + private readonly IGraphQLTextSerializer _documentSerializer; - public GraphQLLocalExecutionClient(TSchema schema, IDocumentExecuter documentExecuter, IGraphQLJsonSerializer serializer, IGraphQLTextSerializer documentSerializer) - { - Schema = schema ?? throw new ArgumentNullException(nameof(schema), "no schema configured"); - _documentExecuter = documentExecuter; - Serializer = serializer ?? throw new ArgumentNullException(nameof(serializer), "please configure the JSON serializer you want to use"); - _documentSerializer = documentSerializer; + public GraphQLLocalExecutionClient(TSchema schema, IDocumentExecuter documentExecuter, IGraphQLJsonSerializer serializer, IGraphQLTextSerializer documentSerializer) + { + Schema = schema ?? throw new ArgumentNullException(nameof(schema), "no schema configured"); + _documentExecuter = documentExecuter; + Serializer = serializer ?? throw new ArgumentNullException(nameof(serializer), "please configure the JSON serializer you want to use"); + _documentSerializer = documentSerializer; - if (!Schema.Initialized) - Schema.Initialize(); - } + if (!Schema.Initialized) + Schema.Initialize(); + } - public void Dispose() { } + public void Dispose() { } - public Task> SendQueryAsync(GraphQLRequest request, CancellationToken cancellationToken = default) - => ExecuteQueryAsync(request, cancellationToken); + public Task> SendQueryAsync(GraphQLRequest request, CancellationToken cancellationToken = default) + => ExecuteQueryAsync(request, cancellationToken); - public Task> SendMutationAsync(GraphQLRequest request, CancellationToken cancellationToken = default) - => ExecuteQueryAsync(request, cancellationToken); + public Task> SendMutationAsync(GraphQLRequest request, CancellationToken cancellationToken = default) + => ExecuteQueryAsync(request, cancellationToken); - public IObservable> CreateSubscriptionStream(GraphQLRequest request) => - Observable.Defer(() => ExecuteSubscriptionAsync(request).ToObservable()) - .Concat() - .Publish() - .RefCount(); + public IObservable> CreateSubscriptionStream(GraphQLRequest request) => + Observable.Defer(() => ExecuteSubscriptionAsync(request).ToObservable()) + .Concat() + .Publish() + .RefCount(); - public IObservable> CreateSubscriptionStream(GraphQLRequest request, - Action exceptionHandler) - => CreateSubscriptionStream(request); + public IObservable> CreateSubscriptionStream(GraphQLRequest request, + Action exceptionHandler) + => CreateSubscriptionStream(request); - #region Private Methods + #region Private Methods - private async Task> ExecuteQueryAsync(GraphQLRequest request, CancellationToken cancellationToken) - { - var executionResult = await ExecuteAsync(request, cancellationToken).ConfigureAwait(false); - return await ExecutionResultToGraphQLResponseAsync(executionResult, cancellationToken).ConfigureAwait(false); - } + private async Task> ExecuteQueryAsync(GraphQLRequest request, CancellationToken cancellationToken) + { + var executionResult = await ExecuteAsync(request, cancellationToken).ConfigureAwait(false); + return await ExecutionResultToGraphQLResponseAsync(executionResult, cancellationToken).ConfigureAwait(false); + } - private async Task>> ExecuteSubscriptionAsync(GraphQLRequest request, CancellationToken cancellationToken = default) - { - var result = await ExecuteAsync(request, cancellationToken).ConfigureAwait(false); - var stream = result.Streams?.Values.SingleOrDefault(); + private async Task>> ExecuteSubscriptionAsync(GraphQLRequest request, CancellationToken cancellationToken = default) + { + var result = await ExecuteAsync(request, cancellationToken).ConfigureAwait(false); + var stream = result.Streams?.Values.SingleOrDefault(); - return stream == null - ? Observable.Throw>(new InvalidOperationException("the GraphQL execution did not return an observable")) - : stream.SelectMany(executionResult => Observable.FromAsync(token => ExecutionResultToGraphQLResponseAsync(executionResult, token))); - } + return stream == null + ? Observable.Throw>(new InvalidOperationException("the GraphQL execution did not return an observable")) + : stream.SelectMany(executionResult => Observable.FromAsync(token => ExecutionResultToGraphQLResponseAsync(executionResult, token))); + } - private async Task ExecuteAsync(GraphQLRequest clientRequest, CancellationToken cancellationToken = default) - { - var serverRequest = _documentSerializer.Deserialize(Serializer.SerializeToString(clientRequest)); - - var result = await _documentExecuter.ExecuteAsync(options => - { - options.Schema = Schema; - options.OperationName = serverRequest?.OperationName; - options.Query = serverRequest?.Query; - options.Variables = serverRequest?.Variables; - options.Extensions = serverRequest?.Extensions; - options.CancellationToken = cancellationToken; - }).ConfigureAwait(false); - - return result; - } - - private async Task> ExecutionResultToGraphQLResponseAsync(ExecutionResult executionResult, CancellationToken cancellationToken = default) + private async Task ExecuteAsync(GraphQLRequest clientRequest, CancellationToken cancellationToken = default) + { + var serverRequest = _documentSerializer.Deserialize(Serializer.SerializeToString(clientRequest)); + + var result = await _documentExecuter.ExecuteAsync(options => { - using var stream = new MemoryStream(); - await _documentSerializer.WriteAsync(stream, executionResult, cancellationToken).ConfigureAwait(false); - stream.Seek(0, SeekOrigin.Begin); - return await Serializer.DeserializeFromUtf8StreamAsync(stream, cancellationToken).ConfigureAwait(false); - } + options.Schema = Schema; + options.OperationName = serverRequest?.OperationName; + options.Query = serverRequest?.Query; + options.Variables = serverRequest?.Variables; + options.Extensions = serverRequest?.Extensions; + options.CancellationToken = cancellationToken; + }).ConfigureAwait(false); + + return result; + } - #endregion + private async Task> ExecutionResultToGraphQLResponseAsync(ExecutionResult executionResult, CancellationToken cancellationToken = default) + { + using var stream = new MemoryStream(); + await _documentSerializer.WriteAsync(stream, executionResult, cancellationToken).ConfigureAwait(false); + stream.Seek(0, SeekOrigin.Begin); + return await Serializer.DeserializeFromUtf8StreamAsync(stream, cancellationToken).ConfigureAwait(false); } + + #endregion } diff --git a/src/GraphQL.Client.LocalExecution/ServiceCollectionExtensions.cs b/src/GraphQL.Client.LocalExecution/ServiceCollectionExtensions.cs index ff18f49d..d5d4a7fb 100644 --- a/src/GraphQL.Client.LocalExecution/ServiceCollectionExtensions.cs +++ b/src/GraphQL.Client.LocalExecution/ServiceCollectionExtensions.cs @@ -4,15 +4,14 @@ using GraphQL.Types; using Microsoft.Extensions.DependencyInjection; -namespace GraphQL.Client.LocalExecution +namespace GraphQL.Client.LocalExecution; + +public static class ServiceCollectionExtensions { - public static class ServiceCollectionExtensions + public static IGraphQLBuilder AddGraphQLLocalExecutionClient(this IServiceCollection services) where TSchema : ISchema { - public static IGraphQLBuilder AddGraphQLLocalExecutionClient(this IServiceCollection services) where TSchema : ISchema - { - services.AddSingleton>(); - services.AddSingleton(p => p.GetRequiredService>()); - return new GraphQLBuilder(services, null); - } + services.AddSingleton>(); + services.AddSingleton(p => p.GetRequiredService>()); + return new GraphQLBuilder(services, null); } } diff --git a/src/GraphQL.Client.Serializer.Newtonsoft/ConstantCaseEnumConverter.cs b/src/GraphQL.Client.Serializer.Newtonsoft/ConstantCaseEnumConverter.cs index 476eed9c..d8e73a5b 100644 --- a/src/GraphQL.Client.Serializer.Newtonsoft/ConstantCaseEnumConverter.cs +++ b/src/GraphQL.Client.Serializer.Newtonsoft/ConstantCaseEnumConverter.cs @@ -3,32 +3,31 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; -namespace GraphQL.Client.Serializer.Newtonsoft +namespace GraphQL.Client.Serializer.Newtonsoft; + +public class ConstantCaseEnumConverter : StringEnumConverter { - public class ConstantCaseEnumConverter : StringEnumConverter + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + if (value == null) + { + writer.WriteNull(); + } + else { - if (value == null) + var enumString = ((Enum)value).ToString("G"); + var memberName = value.GetType() + .GetMember(enumString, BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public) + .FirstOrDefault()?.Name; + if (string.IsNullOrEmpty(memberName)) { - writer.WriteNull(); + if (!AllowIntegerValues) + throw new JsonSerializationException($"Integer value {value} is not allowed."); + writer.WriteValue(value); } else { - var enumString = ((Enum)value).ToString("G"); - var memberName = value.GetType() - .GetMember(enumString, BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public) - .FirstOrDefault()?.Name; - if (string.IsNullOrEmpty(memberName)) - { - if (!AllowIntegerValues) - throw new JsonSerializationException($"Integer value {value} is not allowed."); - writer.WriteValue(value); - } - else - { - writer.WriteValue(memberName.ToConstantCase()); - } + writer.WriteValue(memberName.ToConstantCase()); } } } diff --git a/src/GraphQL.Client.Serializer.Newtonsoft/MapConverter.cs b/src/GraphQL.Client.Serializer.Newtonsoft/MapConverter.cs index d1dd60f4..90b22ed8 100644 --- a/src/GraphQL.Client.Serializer.Newtonsoft/MapConverter.cs +++ b/src/GraphQL.Client.Serializer.Newtonsoft/MapConverter.cs @@ -1,61 +1,60 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; -namespace GraphQL.Client.Serializer.Newtonsoft +namespace GraphQL.Client.Serializer.Newtonsoft; + +public class MapConverter : JsonConverter { - public class MapConverter : JsonConverter - { - public override void WriteJson(JsonWriter writer, Map value, JsonSerializer serializer) => - throw new NotImplementedException( - "This converter currently is only intended to be used to read a JSON object into a strongly-typed representation."); + public override void WriteJson(JsonWriter writer, Map value, JsonSerializer serializer) => + throw new NotImplementedException( + "This converter currently is only intended to be used to read a JSON object into a strongly-typed representation."); - public override Map ReadJson(JsonReader reader, Type objectType, Map existingValue, bool hasExistingValue, JsonSerializer serializer) + public override Map ReadJson(JsonReader reader, Type objectType, Map existingValue, bool hasExistingValue, JsonSerializer serializer) + { + var rootToken = JToken.ReadFrom(reader); + if (rootToken is JObject) { - var rootToken = JToken.ReadFrom(reader); - if (rootToken is JObject) - { - return (Map)ReadDictionary(rootToken, new Map()); - } - else - throw new ArgumentException("This converter can only parse when the root element is a JSON Object."); + return (Map)ReadDictionary(rootToken, new Map()); } + else + throw new ArgumentException("This converter can only parse when the root element is a JSON Object."); + } - private object? ReadToken(JToken? token) => - token switch - { - JObject jObject => ReadDictionary(jObject, new Dictionary()), - JArray jArray => ReadArray(jArray).ToList(), - JValue jValue => jValue.Value, - JConstructor _ => throw new ArgumentOutOfRangeException(nameof(token.Type), - "cannot deserialize a JSON constructor"), - JProperty _ => throw new ArgumentOutOfRangeException(nameof(token.Type), - "cannot deserialize a JSON property"), - JContainer _ => throw new ArgumentOutOfRangeException(nameof(token.Type), - "cannot deserialize a JSON comment"), - _ => throw new ArgumentOutOfRangeException(nameof(token.Type)) - }; + private object? ReadToken(JToken? token) => + token switch + { + JObject jObject => ReadDictionary(jObject, new Dictionary()), + JArray jArray => ReadArray(jArray).ToList(), + JValue jValue => jValue.Value, + JConstructor _ => throw new ArgumentOutOfRangeException(nameof(token.Type), + "cannot deserialize a JSON constructor"), + JProperty _ => throw new ArgumentOutOfRangeException(nameof(token.Type), + "cannot deserialize a JSON property"), + JContainer _ => throw new ArgumentOutOfRangeException(nameof(token.Type), + "cannot deserialize a JSON comment"), + _ => throw new ArgumentOutOfRangeException(nameof(token.Type)) + }; - private Dictionary ReadDictionary(JToken element, Dictionary to) + private Dictionary ReadDictionary(JToken element, Dictionary to) + { + foreach (var property in ((JObject)element).Properties()) { - foreach (var property in ((JObject)element).Properties()) - { - if (IsUnsupportedJTokenType(property.Value.Type)) - continue; - to[property.Name] = ReadToken(property.Value); - } - return to; + if (IsUnsupportedJTokenType(property.Value.Type)) + continue; + to[property.Name] = ReadToken(property.Value); } + return to; + } - private IEnumerable ReadArray(JArray element) + private IEnumerable ReadArray(JArray element) + { + foreach (var item in element) { - foreach (var item in element) - { - if (IsUnsupportedJTokenType(item.Type)) - continue; - yield return ReadToken(item); - } + if (IsUnsupportedJTokenType(item.Type)) + continue; + yield return ReadToken(item); } - - private bool IsUnsupportedJTokenType(JTokenType type) => type == JTokenType.Constructor || type == JTokenType.Property || type == JTokenType.Comment; } + + private bool IsUnsupportedJTokenType(JTokenType type) => type == JTokenType.Constructor || type == JTokenType.Property || type == JTokenType.Comment; } diff --git a/src/GraphQL.Client.Serializer.Newtonsoft/NewtonsoftJsonSerializer.cs b/src/GraphQL.Client.Serializer.Newtonsoft/NewtonsoftJsonSerializer.cs index 6e362c08..63cc6de3 100644 --- a/src/GraphQL.Client.Serializer.Newtonsoft/NewtonsoftJsonSerializer.cs +++ b/src/GraphQL.Client.Serializer.Newtonsoft/NewtonsoftJsonSerializer.cs @@ -4,54 +4,53 @@ using Newtonsoft.Json; using Newtonsoft.Json.Serialization; -namespace GraphQL.Client.Serializer.Newtonsoft +namespace GraphQL.Client.Serializer.Newtonsoft; + +public class NewtonsoftJsonSerializer : IGraphQLWebsocketJsonSerializer { - public class NewtonsoftJsonSerializer : IGraphQLWebsocketJsonSerializer + public static JsonSerializerSettings DefaultJsonSerializerSettings => new JsonSerializerSettings { - public static JsonSerializerSettings DefaultJsonSerializerSettings => new JsonSerializerSettings - { - ContractResolver = new CamelCasePropertyNamesContractResolver { IgnoreIsSpecifiedMembers = true }, - MissingMemberHandling = MissingMemberHandling.Ignore, - Converters = { new ConstantCaseEnumConverter() } - }; + ContractResolver = new CamelCasePropertyNamesContractResolver { IgnoreIsSpecifiedMembers = true }, + MissingMemberHandling = MissingMemberHandling.Ignore, + Converters = { new ConstantCaseEnumConverter() } + }; - public JsonSerializerSettings JsonSerializerSettings { get; } + public JsonSerializerSettings JsonSerializerSettings { get; } - public NewtonsoftJsonSerializer() : this(DefaultJsonSerializerSettings) { } + public NewtonsoftJsonSerializer() : this(DefaultJsonSerializerSettings) { } - public NewtonsoftJsonSerializer(Action configure) : this(configure.AndReturn(DefaultJsonSerializerSettings)) { } + public NewtonsoftJsonSerializer(Action configure) : this(configure.AndReturn(DefaultJsonSerializerSettings)) { } - public NewtonsoftJsonSerializer(JsonSerializerSettings jsonSerializerSettings) - { - JsonSerializerSettings = jsonSerializerSettings; - ConfigureMandatorySerializerOptions(); - } + public NewtonsoftJsonSerializer(JsonSerializerSettings jsonSerializerSettings) + { + JsonSerializerSettings = jsonSerializerSettings; + ConfigureMandatorySerializerOptions(); + } - // deserialize extensions to Dictionary - private void ConfigureMandatorySerializerOptions() => JsonSerializerSettings.Converters.Insert(0, new MapConverter()); + // deserialize extensions to Dictionary + private void ConfigureMandatorySerializerOptions() => JsonSerializerSettings.Converters.Insert(0, new MapConverter()); - public string SerializeToString(GraphQLRequest request) => JsonConvert.SerializeObject(request, JsonSerializerSettings); + public string SerializeToString(GraphQLRequest request) => JsonConvert.SerializeObject(request, JsonSerializerSettings); - public byte[] SerializeToBytes(GraphQLWebSocketRequest request) - { - var json = JsonConvert.SerializeObject(request, JsonSerializerSettings); - return Encoding.UTF8.GetBytes(json); - } + public byte[] SerializeToBytes(GraphQLWebSocketRequest request) + { + var json = JsonConvert.SerializeObject(request, JsonSerializerSettings); + return Encoding.UTF8.GetBytes(json); + } - public Task DeserializeToWebsocketResponseWrapperAsync(Stream stream) => DeserializeFromUtf8Stream(stream); + public Task DeserializeToWebsocketResponseWrapperAsync(Stream stream) => DeserializeFromUtf8Stream(stream); - public GraphQLWebSocketResponse> DeserializeToWebsocketResponse(byte[] bytes) => - JsonConvert.DeserializeObject>>(Encoding.UTF8.GetString(bytes), - JsonSerializerSettings); + public GraphQLWebSocketResponse> DeserializeToWebsocketResponse(byte[] bytes) => + JsonConvert.DeserializeObject>>(Encoding.UTF8.GetString(bytes), + JsonSerializerSettings); - public Task> DeserializeFromUtf8StreamAsync(Stream stream, CancellationToken cancellationToken) => DeserializeFromUtf8Stream>(stream); + public Task> DeserializeFromUtf8StreamAsync(Stream stream, CancellationToken cancellationToken) => DeserializeFromUtf8Stream>(stream); - private Task DeserializeFromUtf8Stream(Stream stream) - { - using var sr = new StreamReader(stream); - using JsonReader reader = new JsonTextReader(sr); - var serializer = JsonSerializer.Create(JsonSerializerSettings); - return Task.FromResult(serializer.Deserialize(reader)); - } + private Task DeserializeFromUtf8Stream(Stream stream) + { + using var sr = new StreamReader(stream); + using JsonReader reader = new JsonTextReader(sr); + var serializer = JsonSerializer.Create(JsonSerializerSettings); + return Task.FromResult(serializer.Deserialize(reader)); } } diff --git a/src/GraphQL.Client.Serializer.SystemTextJson/ConstantCaseJsonNamingPolicy.cs b/src/GraphQL.Client.Serializer.SystemTextJson/ConstantCaseJsonNamingPolicy.cs index 138b0276..680503a6 100644 --- a/src/GraphQL.Client.Serializer.SystemTextJson/ConstantCaseJsonNamingPolicy.cs +++ b/src/GraphQL.Client.Serializer.SystemTextJson/ConstantCaseJsonNamingPolicy.cs @@ -1,10 +1,9 @@ using System.Text.Json; using GraphQL.Client.Abstractions.Utilities; -namespace GraphQL.Client.Serializer.SystemTextJson +namespace GraphQL.Client.Serializer.SystemTextJson; + +public class ConstantCaseJsonNamingPolicy: JsonNamingPolicy { - public class ConstantCaseJsonNamingPolicy: JsonNamingPolicy - { - public override string ConvertName(string name) => name.ToConstantCase(); - } + public override string ConvertName(string name) => name.ToConstantCase(); } diff --git a/src/GraphQL.Client.Serializer.SystemTextJson/ConverterHelperExtensions.cs b/src/GraphQL.Client.Serializer.SystemTextJson/ConverterHelperExtensions.cs index 7afffe57..6424e91d 100644 --- a/src/GraphQL.Client.Serializer.SystemTextJson/ConverterHelperExtensions.cs +++ b/src/GraphQL.Client.Serializer.SystemTextJson/ConverterHelperExtensions.cs @@ -3,34 +3,33 @@ using System.Text; using System.Text.Json; -namespace GraphQL.Client.Serializer.SystemTextJson +namespace GraphQL.Client.Serializer.SystemTextJson; + +public static class ConverterHelperExtensions { - public static class ConverterHelperExtensions + public static object ReadNumber(this ref Utf8JsonReader reader) { - public static object ReadNumber(this ref Utf8JsonReader reader) - { - if (reader.TryGetInt32(out int i)) - return i; - else if (reader.TryGetInt64(out long l)) - return l; - else if (reader.TryGetDouble(out double d)) - return reader.TryGetBigInteger(out var bi) && bi != new BigInteger(d) - ? bi - : (object)d; - else if (reader.TryGetDecimal(out decimal dd)) - return reader.TryGetBigInteger(out var bi) && bi != new BigInteger(dd) - ? bi - : (object)dd; + if (reader.TryGetInt32(out int i)) + return i; + else if (reader.TryGetInt64(out long l)) + return l; + else if (reader.TryGetDouble(out double d)) + return reader.TryGetBigInteger(out var bi) && bi != new BigInteger(d) + ? bi + : (object)d; + else if (reader.TryGetDecimal(out decimal dd)) + return reader.TryGetBigInteger(out var bi) && bi != new BigInteger(dd) + ? bi + : (object)dd; - throw new NotImplementedException($"Unexpected Number value. Raw text was: {reader.GetRawString()}"); - } + throw new NotImplementedException($"Unexpected Number value. Raw text was: {reader.GetRawString()}"); + } - public static bool TryGetBigInteger(this ref Utf8JsonReader reader, out BigInteger bi) => BigInteger.TryParse(reader.GetRawString(), out bi); + public static bool TryGetBigInteger(this ref Utf8JsonReader reader, out BigInteger bi) => BigInteger.TryParse(reader.GetRawString(), out bi); - public static string GetRawString(this ref Utf8JsonReader reader) - { - var byteArray = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan.ToArray(); - return Encoding.UTF8.GetString(byteArray); - } + public static string GetRawString(this ref Utf8JsonReader reader) + { + var byteArray = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan.ToArray(); + return Encoding.UTF8.GetString(byteArray); } } diff --git a/src/GraphQL.Client.Serializer.SystemTextJson/ErrorPathConverter.cs b/src/GraphQL.Client.Serializer.SystemTextJson/ErrorPathConverter.cs index 4114a17a..e325dccb 100644 --- a/src/GraphQL.Client.Serializer.SystemTextJson/ErrorPathConverter.cs +++ b/src/GraphQL.Client.Serializer.SystemTextJson/ErrorPathConverter.cs @@ -1,48 +1,47 @@ using System.Text.Json; using System.Text.Json.Serialization; -namespace GraphQL.Client.Serializer.SystemTextJson +namespace GraphQL.Client.Serializer.SystemTextJson; + +public class ErrorPathConverter : JsonConverter { - public class ErrorPathConverter : JsonConverter - { - public override ErrorPath Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => - new ErrorPath(ReadArray(ref reader)); + public override ErrorPath Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => + new ErrorPath(ReadArray(ref reader)); - public override void Write(Utf8JsonWriter writer, ErrorPath value, JsonSerializerOptions options) - => throw new NotImplementedException( - "This converter currently is only intended to be used to read a JSON object into a strongly-typed representation."); - - private IEnumerable ReadArray(ref Utf8JsonReader reader) + public override void Write(Utf8JsonWriter writer, ErrorPath value, JsonSerializerOptions options) + => throw new NotImplementedException( + "This converter currently is only intended to be used to read a JSON object into a strongly-typed representation."); + + private IEnumerable ReadArray(ref Utf8JsonReader reader) + { + if (reader.TokenType != JsonTokenType.StartArray) { - if (reader.TokenType != JsonTokenType.StartArray) - { - throw new JsonException("This converter can only parse when the root element is a JSON Array."); - } - - var array = new List(); + throw new JsonException("This converter can only parse when the root element is a JSON Array."); + } - while (reader.Read()) - { - if (reader.TokenType == JsonTokenType.EndArray) - break; + var array = new List(); - array.Add(ReadValue(ref reader)); - } + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndArray) + break; - return array; + array.Add(ReadValue(ref reader)); } - private object? ReadValue(ref Utf8JsonReader reader) - => reader.TokenType switch - { - JsonTokenType.None => null, - JsonTokenType.String => reader.GetString(), - JsonTokenType.Number => reader.ReadNumber(), - JsonTokenType.True => true, - JsonTokenType.False => false, - JsonTokenType.Null => null, - _ => throw new InvalidOperationException($"Unexpected token type: {reader.TokenType}") - }; + return array; } + + private object? ReadValue(ref Utf8JsonReader reader) + => reader.TokenType switch + { + JsonTokenType.None => null, + JsonTokenType.String => reader.GetString(), + JsonTokenType.Number => reader.ReadNumber(), + JsonTokenType.True => true, + JsonTokenType.False => false, + JsonTokenType.Null => null, + _ => throw new InvalidOperationException($"Unexpected token type: {reader.TokenType}") + }; } diff --git a/src/GraphQL.Client.Serializer.SystemTextJson/ImmutableConverter.cs b/src/GraphQL.Client.Serializer.SystemTextJson/ImmutableConverter.cs index 0c9ec398..96bad2c6 100644 --- a/src/GraphQL.Client.Serializer.SystemTextJson/ImmutableConverter.cs +++ b/src/GraphQL.Client.Serializer.SystemTextJson/ImmutableConverter.cs @@ -3,183 +3,182 @@ using System.Text.Json; using System.Text.Json.Serialization; -namespace GraphQL.Client.Serializer.SystemTextJson +namespace GraphQL.Client.Serializer.SystemTextJson; + +/// +/// class for converting immutable objects, derived from https://github.com/manne/obviously/blob/master/src/system.text.json/Core/ImmutableConverter.cs +/// +public class ImmutableConverter : JsonConverter { - /// - /// class for converting immutable objects, derived from https://github.com/manne/obviously/blob/master/src/system.text.json/Core/ImmutableConverter.cs - /// - public class ImmutableConverter : JsonConverter + public override bool CanConvert(Type typeToConvert) { - public override bool CanConvert(Type typeToConvert) - { - if (typeToConvert.IsPrimitive) - return false; + if (typeToConvert.IsPrimitive) + return false; - var nullableUnderlyingType = Nullable.GetUnderlyingType(typeToConvert); - if (nullableUnderlyingType != null && nullableUnderlyingType.IsValueType) - return false; + var nullableUnderlyingType = Nullable.GetUnderlyingType(typeToConvert); + if (nullableUnderlyingType != null && nullableUnderlyingType.IsValueType) + return false; - bool result; - var constructors = typeToConvert.GetConstructors(BindingFlags.Public | BindingFlags.Instance); - if (constructors.Length != 1) + bool result; + var constructors = typeToConvert.GetConstructors(BindingFlags.Public | BindingFlags.Instance); + if (constructors.Length != 1) + { + result = false; + } + else + { + var constructor = constructors[0]; + var parameters = constructor.GetParameters(); + + if (parameters.Length > 0) { - result = false; + var properties = typeToConvert.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + result = parameters + .Select(parameter => properties.Any(p => NameOfPropertyAndParameter.Matches(p.Name, parameter.Name, typeToConvert.IsAnonymous()))) + .All(hasMatchingProperty => hasMatchingProperty); } else { - var constructor = constructors[0]; - var parameters = constructor.GetParameters(); - - if (parameters.Length > 0) - { - var properties = typeToConvert.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); - result = parameters - .Select(parameter => properties.Any(p => NameOfPropertyAndParameter.Matches(p.Name, parameter.Name, typeToConvert.IsAnonymous()))) - .All(hasMatchingProperty => hasMatchingProperty); - } - else - { - result = false; - } + result = false; } - - return result; } - public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + return result; + } + + public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var valueOfProperty = new Dictionary(); + var namedPropertiesMapping = GetNamedProperties(options, GetProperties(typeToConvert)); + reader.Read(); + while (true) { - var valueOfProperty = new Dictionary(); - var namedPropertiesMapping = GetNamedProperties(options, GetProperties(typeToConvert)); - reader.Read(); - while (true) + if (reader.TokenType != JsonTokenType.PropertyName && reader.TokenType != JsonTokenType.String) { - if (reader.TokenType != JsonTokenType.PropertyName && reader.TokenType != JsonTokenType.String) - { - break; - } - - string jsonPropName = reader.GetString(); - string normalizedPropName = ConvertAndNormalizeName(jsonPropName, options); - if (!namedPropertiesMapping.TryGetValue(normalizedPropName, out var obProp)) - { - reader.Read(); - } - else - { - var value = JsonSerializer.Deserialize(ref reader, obProp.PropertyType, options); - reader.Read(); - valueOfProperty[obProp] = value; - } + break; } - var ctor = typeToConvert.GetConstructors(BindingFlags.Public | BindingFlags.Instance).First(); - var parameters = ctor.GetParameters(); - var parameterValues = new object[parameters.Length]; - for (int index = 0; index < parameters.Length; index++) + string jsonPropName = reader.GetString(); + string normalizedPropName = ConvertAndNormalizeName(jsonPropName, options); + if (!namedPropertiesMapping.TryGetValue(normalizedPropName, out var obProp)) { - var parameterInfo = parameters[index]; - var value = valueOfProperty.First(prop => - NameOfPropertyAndParameter.Matches(prop.Key.Name, parameterInfo.Name, typeToConvert.IsAnonymous())).Value; - - parameterValues[index] = value; + reader.Read(); } - - var instance = ctor.Invoke(parameterValues); - return instance; - } - - public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) - { - var strippedOptions = new JsonSerializerOptions - { - AllowTrailingCommas = options.AllowTrailingCommas, - DefaultBufferSize = options.DefaultBufferSize, - DictionaryKeyPolicy = options.DictionaryKeyPolicy, - Encoder = options.Encoder, - IgnoreNullValues = options.IgnoreNullValues, - IgnoreReadOnlyProperties = options.IgnoreReadOnlyProperties, - MaxDepth = options.MaxDepth, - PropertyNameCaseInsensitive = options.PropertyNameCaseInsensitive, - PropertyNamingPolicy = options.PropertyNamingPolicy, - ReadCommentHandling = options.ReadCommentHandling, - WriteIndented = options.WriteIndented - }; - foreach (var converter in options.Converters) + else { - if (!(converter is ImmutableConverter)) - strippedOptions.Converters.Add(converter); + var value = JsonSerializer.Deserialize(ref reader, obProp.PropertyType, options); + reader.Read(); + valueOfProperty[obProp] = value; } - - JsonSerializer.Serialize(writer, value, strippedOptions); } - private static PropertyInfo[] GetProperties(IReflect typeToConvert) => typeToConvert.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); - - private static IReadOnlyDictionary GetNamedProperties(JsonSerializerOptions options, IEnumerable properties) + var ctor = typeToConvert.GetConstructors(BindingFlags.Public | BindingFlags.Instance).First(); + var parameters = ctor.GetParameters(); + var parameterValues = new object[parameters.Length]; + for (int index = 0; index < parameters.Length; index++) { - var result = new Dictionary(); - foreach (var property in properties) - { - string name; - var nameAttribute = property.GetCustomAttribute(); - if (nameAttribute != null) - { - name = nameAttribute.Name; - } - else - { - name = options.PropertyNamingPolicy?.ConvertName(property.Name) ?? property.Name; - } - - string normalizedName = NormalizeName(name, options); - result.Add(normalizedName, property); - } + var parameterInfo = parameters[index]; + var value = valueOfProperty.First(prop => + NameOfPropertyAndParameter.Matches(prop.Key.Name, parameterInfo.Name, typeToConvert.IsAnonymous())).Value; - return result; + parameterValues[index] = value; } - private static string ConvertAndNormalizeName(string name, JsonSerializerOptions options) + var instance = ctor.Invoke(parameterValues); + return instance; + } + + public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) + { + var strippedOptions = new JsonSerializerOptions { - string convertedName = options.PropertyNamingPolicy?.ConvertName(name) ?? name; - return NormalizeName(convertedName, options); + AllowTrailingCommas = options.AllowTrailingCommas, + DefaultBufferSize = options.DefaultBufferSize, + DictionaryKeyPolicy = options.DictionaryKeyPolicy, + Encoder = options.Encoder, + IgnoreNullValues = options.IgnoreNullValues, + IgnoreReadOnlyProperties = options.IgnoreReadOnlyProperties, + MaxDepth = options.MaxDepth, + PropertyNameCaseInsensitive = options.PropertyNameCaseInsensitive, + PropertyNamingPolicy = options.PropertyNamingPolicy, + ReadCommentHandling = options.ReadCommentHandling, + WriteIndented = options.WriteIndented + }; + foreach (var converter in options.Converters) + { + if (!(converter is ImmutableConverter)) + strippedOptions.Converters.Add(converter); } - private static string NormalizeName(string name, JsonSerializerOptions options) => options.PropertyNameCaseInsensitive ? name.ToLowerInvariant() : name; + JsonSerializer.Serialize(writer, value, strippedOptions); } - internal static class NameOfPropertyAndParameter + private static PropertyInfo[] GetProperties(IReflect typeToConvert) => typeToConvert.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + + private static IReadOnlyDictionary GetNamedProperties(JsonSerializerOptions options, IEnumerable properties) { - public static bool Matches(string propertyName, string parameterName, bool anonymousType) + var result = new Dictionary(); + foreach (var property in properties) { - if (string.IsNullOrEmpty(propertyName)) + string name; + var nameAttribute = property.GetCustomAttribute(); + if (nameAttribute != null) { - return string.IsNullOrEmpty(parameterName); + name = nameAttribute.Name; } - - if (string.IsNullOrEmpty(parameterName)) - { - return false; - } - - if (anonymousType) + else { - return propertyName.Equals(parameterName, StringComparison.Ordinal); + name = options.PropertyNamingPolicy?.ConvertName(property.Name) ?? property.Name; } - var xRight = propertyName.AsSpan(1); - var yRight = parameterName.AsSpan(1); - return char.ToLowerInvariant(propertyName[0]).CompareTo(parameterName[0]) == 0 && xRight.Equals(yRight, StringComparison.Ordinal); + string normalizedName = NormalizeName(name, options); + result.Add(normalizedName, property); } + + return result; + } + + private static string ConvertAndNormalizeName(string name, JsonSerializerOptions options) + { + string convertedName = options.PropertyNamingPolicy?.ConvertName(name) ?? name; + return NormalizeName(convertedName, options); } - internal static class TypeExtensions + private static string NormalizeName(string name, JsonSerializerOptions options) => options.PropertyNameCaseInsensitive ? name.ToLowerInvariant() : name; +} + +internal static class NameOfPropertyAndParameter +{ + public static bool Matches(string propertyName, string parameterName, bool anonymousType) { - // copied from https://github.com/dahomey-technologies/Dahomey.Json/blob/master/src/Dahomey.Json/Util/TypeExtensions.cs - public static bool IsAnonymous(this Type type) => - type.Namespace == null - && type.IsSealed - && type.BaseType == typeof(object) - && !type.IsPublic - && type.IsDefined(typeof(CompilerGeneratedAttribute), false); + if (string.IsNullOrEmpty(propertyName)) + { + return string.IsNullOrEmpty(parameterName); + } + + if (string.IsNullOrEmpty(parameterName)) + { + return false; + } + + if (anonymousType) + { + return propertyName.Equals(parameterName, StringComparison.Ordinal); + } + + var xRight = propertyName.AsSpan(1); + var yRight = parameterName.AsSpan(1); + return char.ToLowerInvariant(propertyName[0]).CompareTo(parameterName[0]) == 0 && xRight.Equals(yRight, StringComparison.Ordinal); } } + +internal static class TypeExtensions +{ + // copied from https://github.com/dahomey-technologies/Dahomey.Json/blob/master/src/Dahomey.Json/Util/TypeExtensions.cs + public static bool IsAnonymous(this Type type) => + type.Namespace == null + && type.IsSealed + && type.BaseType == typeof(object) + && !type.IsPublic + && type.IsDefined(typeof(CompilerGeneratedAttribute), false); +} diff --git a/src/GraphQL.Client.Serializer.SystemTextJson/JsonSerializerOptionsExtensions.cs b/src/GraphQL.Client.Serializer.SystemTextJson/JsonSerializerOptionsExtensions.cs index bc2ddc14..3fc8f6eb 100644 --- a/src/GraphQL.Client.Serializer.SystemTextJson/JsonSerializerOptionsExtensions.cs +++ b/src/GraphQL.Client.Serializer.SystemTextJson/JsonSerializerOptionsExtensions.cs @@ -1,13 +1,12 @@ using System.Text.Json; -namespace GraphQL.Client.Serializer.SystemTextJson +namespace GraphQL.Client.Serializer.SystemTextJson; + +public static class JsonSerializerOptionsExtensions { - public static class JsonSerializerOptionsExtensions + public static JsonSerializerOptions SetupImmutableConverter(this JsonSerializerOptions options) { - public static JsonSerializerOptions SetupImmutableConverter(this JsonSerializerOptions options) - { - options.Converters.Add(new ImmutableConverter()); - return options; - } + options.Converters.Add(new ImmutableConverter()); + return options; } } diff --git a/src/GraphQL.Client.Serializer.SystemTextJson/MapConverter.cs b/src/GraphQL.Client.Serializer.SystemTextJson/MapConverter.cs index e3ab7217..66d4aead 100644 --- a/src/GraphQL.Client.Serializer.SystemTextJson/MapConverter.cs +++ b/src/GraphQL.Client.Serializer.SystemTextJson/MapConverter.cs @@ -1,80 +1,79 @@ using System.Text.Json; using System.Text.Json.Serialization; -namespace GraphQL.Client.Serializer.SystemTextJson +namespace GraphQL.Client.Serializer.SystemTextJson; + +/// +/// A custom JsonConverter for reading the extension fields of and . +/// +/// +/// Taken and modified from GraphQL.SystemTextJson.ObjectDictionaryConverter (GraphQL.NET) +/// +public class MapConverter : JsonConverter { - /// - /// A custom JsonConverter for reading the extension fields of and . - /// - /// - /// Taken and modified from GraphQL.SystemTextJson.ObjectDictionaryConverter (GraphQL.NET) - /// - public class MapConverter : JsonConverter - { - public override Map Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => ReadDictionary(ref reader, new Map()); + public override Map Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => ReadDictionary(ref reader, new Map()); - public override void Write(Utf8JsonWriter writer, Map value, JsonSerializerOptions options) - => throw new NotImplementedException( - "This converter currently is only intended to be used to read a JSON object into a strongly-typed representation."); + public override void Write(Utf8JsonWriter writer, Map value, JsonSerializerOptions options) + => throw new NotImplementedException( + "This converter currently is only intended to be used to read a JSON object into a strongly-typed representation."); - private static TDictionary ReadDictionary(ref Utf8JsonReader reader, TDictionary result) - where TDictionary : Dictionary + private static TDictionary ReadDictionary(ref Utf8JsonReader reader, TDictionary result) + where TDictionary : Dictionary + { + if (reader.TokenType != JsonTokenType.StartObject) + throw new JsonException(); + + while (reader.Read()) { - if (reader.TokenType != JsonTokenType.StartObject) - throw new JsonException(); - - while (reader.Read()) - { - if (reader.TokenType == JsonTokenType.EndObject) - break; + if (reader.TokenType == JsonTokenType.EndObject) + break; - if (reader.TokenType != JsonTokenType.PropertyName) - throw new JsonException(); - - string key = reader.GetString(); + if (reader.TokenType != JsonTokenType.PropertyName) + throw new JsonException(); - // move to property value - if (!reader.Read()) - throw new JsonException(); + string key = reader.GetString(); - result.Add(key, ReadValue(ref reader)); - } + // move to property value + if (!reader.Read()) + throw new JsonException(); - return result; + result.Add(key, ReadValue(ref reader)); } - private static List ReadArray(ref Utf8JsonReader reader) - { - if (reader.TokenType != JsonTokenType.StartArray) - throw new JsonException(); + return result; + } - var result = new List(); + private static List ReadArray(ref Utf8JsonReader reader) + { + if (reader.TokenType != JsonTokenType.StartArray) + throw new JsonException(); - while (reader.Read()) - { - if (reader.TokenType == JsonTokenType.EndArray) - break; + var result = new List(); - result.Add(ReadValue(ref reader)); - } + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndArray) + break; - return result; + result.Add(ReadValue(ref reader)); } - - private static object? ReadValue(ref Utf8JsonReader reader) - => reader.TokenType switch - { - JsonTokenType.StartArray => ReadArray(ref reader).ToList(), - JsonTokenType.StartObject => ReadDictionary(ref reader, new Dictionary()), - JsonTokenType.Number => reader.ReadNumber(), - JsonTokenType.True => true, - JsonTokenType.False => false, - JsonTokenType.String => reader.GetString(), - JsonTokenType.Null => null, - JsonTokenType.None => null, - _ => throw new InvalidOperationException($"Unexpected value kind: {reader.TokenType}") - }; - + return result; } + + private static object? ReadValue(ref Utf8JsonReader reader) + => reader.TokenType switch + { + JsonTokenType.StartArray => ReadArray(ref reader).ToList(), + JsonTokenType.StartObject => ReadDictionary(ref reader, new Dictionary()), + JsonTokenType.Number => reader.ReadNumber(), + JsonTokenType.True => true, + JsonTokenType.False => false, + JsonTokenType.String => reader.GetString(), + JsonTokenType.Null => null, + JsonTokenType.None => null, + _ => throw new InvalidOperationException($"Unexpected value kind: {reader.TokenType}") + }; + + } diff --git a/src/GraphQL.Client.Serializer.SystemTextJson/SystemTextJsonSerializer.cs b/src/GraphQL.Client.Serializer.SystemTextJson/SystemTextJsonSerializer.cs index 05b2c2f0..4fa66c64 100644 --- a/src/GraphQL.Client.Serializer.SystemTextJson/SystemTextJsonSerializer.cs +++ b/src/GraphQL.Client.Serializer.SystemTextJson/SystemTextJsonSerializer.cs @@ -3,47 +3,46 @@ using GraphQL.Client.Abstractions; using GraphQL.Client.Abstractions.Websocket; -namespace GraphQL.Client.Serializer.SystemTextJson +namespace GraphQL.Client.Serializer.SystemTextJson; + +public class SystemTextJsonSerializer : IGraphQLWebsocketJsonSerializer { - public class SystemTextJsonSerializer : IGraphQLWebsocketJsonSerializer + public static JsonSerializerOptions DefaultJsonSerializerOptions => new JsonSerializerOptions { - public static JsonSerializerOptions DefaultJsonSerializerOptions => new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - Converters = { new JsonStringEnumConverter(new ConstantCaseJsonNamingPolicy(), false)} - }.SetupImmutableConverter(); + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + Converters = { new JsonStringEnumConverter(new ConstantCaseJsonNamingPolicy(), false)} + }.SetupImmutableConverter(); - public JsonSerializerOptions Options { get; } + public JsonSerializerOptions Options { get; } - public SystemTextJsonSerializer() : this(DefaultJsonSerializerOptions) { } + public SystemTextJsonSerializer() : this(DefaultJsonSerializerOptions) { } - public SystemTextJsonSerializer(Action configure) : this(configure.AndReturn(DefaultJsonSerializerOptions)) { } + public SystemTextJsonSerializer(Action configure) : this(configure.AndReturn(DefaultJsonSerializerOptions)) { } - public SystemTextJsonSerializer(JsonSerializerOptions options) - { - Options = options; - ConfigureMandatorySerializerOptions(); - } + public SystemTextJsonSerializer(JsonSerializerOptions options) + { + Options = options; + ConfigureMandatorySerializerOptions(); + } - private void ConfigureMandatorySerializerOptions() - { - // deserialize extensions to Dictionary - Options.Converters.Insert(0, new ErrorPathConverter()); - Options.Converters.Insert(0, new MapConverter()); - // allow the JSON field "data" to match the property "Data" even without JsonNamingPolicy.CamelCase - Options.PropertyNameCaseInsensitive = true; - } + private void ConfigureMandatorySerializerOptions() + { + // deserialize extensions to Dictionary + Options.Converters.Insert(0, new ErrorPathConverter()); + Options.Converters.Insert(0, new MapConverter()); + // allow the JSON field "data" to match the property "Data" even without JsonNamingPolicy.CamelCase + Options.PropertyNameCaseInsensitive = true; + } - public string SerializeToString(GraphQLRequest request) => JsonSerializer.Serialize(request, Options); + public string SerializeToString(GraphQLRequest request) => JsonSerializer.Serialize(request, Options); - public Task> DeserializeFromUtf8StreamAsync(Stream stream, CancellationToken cancellationToken) => JsonSerializer.DeserializeAsync>(stream, Options, cancellationToken).AsTask(); + public Task> DeserializeFromUtf8StreamAsync(Stream stream, CancellationToken cancellationToken) => JsonSerializer.DeserializeAsync>(stream, Options, cancellationToken).AsTask(); - public byte[] SerializeToBytes(GraphQLWebSocketRequest request) => JsonSerializer.SerializeToUtf8Bytes(request, Options); + public byte[] SerializeToBytes(GraphQLWebSocketRequest request) => JsonSerializer.SerializeToUtf8Bytes(request, Options); - public Task DeserializeToWebsocketResponseWrapperAsync(Stream stream) => JsonSerializer.DeserializeAsync(stream, Options).AsTask(); + public Task DeserializeToWebsocketResponseWrapperAsync(Stream stream) => JsonSerializer.DeserializeAsync(stream, Options).AsTask(); - public GraphQLWebSocketResponse> DeserializeToWebsocketResponse(byte[] bytes) => - JsonSerializer.Deserialize>>(new ReadOnlySpan(bytes), - Options); - } + public GraphQLWebSocketResponse> DeserializeToWebsocketResponse(byte[] bytes) => + JsonSerializer.Deserialize>>(new ReadOnlySpan(bytes), + Options); } diff --git a/src/GraphQL.Client/GraphQLHttpClient.cs b/src/GraphQL.Client/GraphQLHttpClient.cs index a0b20c5d..eda4b6f9 100644 --- a/src/GraphQL.Client/GraphQLHttpClient.cs +++ b/src/GraphQL.Client/GraphQLHttpClient.cs @@ -4,188 +4,187 @@ using GraphQL.Client.Abstractions.Websocket; using GraphQL.Client.Http.Websocket; -namespace GraphQL.Client.Http +namespace GraphQL.Client.Http; + +public class GraphQLHttpClient : IGraphQLClient, IDisposable { - public class GraphQLHttpClient : IGraphQLClient, IDisposable - { - private readonly Lazy _lazyHttpWebSocket; - private GraphQLHttpWebSocket GraphQlHttpWebSocket => _lazyHttpWebSocket.Value; + private readonly Lazy _lazyHttpWebSocket; + private GraphQLHttpWebSocket GraphQlHttpWebSocket => _lazyHttpWebSocket.Value; - private readonly CancellationTokenSource _cancellationTokenSource = new(); + private readonly CancellationTokenSource _cancellationTokenSource = new(); - private readonly bool _disposeHttpClient = false; + private readonly bool _disposeHttpClient = false; - /// - /// the json serializer - /// - public IGraphQLWebsocketJsonSerializer JsonSerializer { get; } + /// + /// the json serializer + /// + public IGraphQLWebsocketJsonSerializer JsonSerializer { get; } - /// - /// the instance of which is used internally - /// - public HttpClient HttpClient { get; } + /// + /// the instance of which is used internally + /// + public HttpClient HttpClient { get; } - /// - /// The Options to be used - /// - public GraphQLHttpClientOptions Options { get; } + /// + /// The Options to be used + /// + public GraphQLHttpClientOptions Options { get; } - /// - /// Publishes all exceptions which occur inside the websocket receive stream (i.e. for logging purposes) - /// - public IObservable WebSocketReceiveErrors => GraphQlHttpWebSocket.ReceiveErrors; + /// + /// Publishes all exceptions which occur inside the websocket receive stream (i.e. for logging purposes) + /// + public IObservable WebSocketReceiveErrors => GraphQlHttpWebSocket.ReceiveErrors; - /// - /// the websocket connection state - /// - public IObservable WebsocketConnectionState => GraphQlHttpWebSocket.ConnectionState; + /// + /// the websocket connection state + /// + public IObservable WebsocketConnectionState => GraphQlHttpWebSocket.ConnectionState; - #region Constructors + #region Constructors - public GraphQLHttpClient(string endPoint, IGraphQLWebsocketJsonSerializer serializer) - : this(new Uri(endPoint), serializer) { } + public GraphQLHttpClient(string endPoint, IGraphQLWebsocketJsonSerializer serializer) + : this(new Uri(endPoint), serializer) { } - public GraphQLHttpClient(Uri endPoint, IGraphQLWebsocketJsonSerializer serializer) - : this(o => o.EndPoint = endPoint, serializer) { } + public GraphQLHttpClient(Uri endPoint, IGraphQLWebsocketJsonSerializer serializer) + : this(o => o.EndPoint = endPoint, serializer) { } - public GraphQLHttpClient(Action configure, IGraphQLWebsocketJsonSerializer serializer) - : this(configure.New(), serializer) { } + public GraphQLHttpClient(Action configure, IGraphQLWebsocketJsonSerializer serializer) + : this(configure.New(), serializer) { } - public GraphQLHttpClient(GraphQLHttpClientOptions options, IGraphQLWebsocketJsonSerializer serializer) - : this(options, serializer, new HttpClient(options.HttpMessageHandler)) - { - // set this flag to dispose the internally created HttpClient when GraphQLHttpClient gets disposed - _disposeHttpClient = true; - } + public GraphQLHttpClient(GraphQLHttpClientOptions options, IGraphQLWebsocketJsonSerializer serializer) + : this(options, serializer, new HttpClient(options.HttpMessageHandler)) + { + // set this flag to dispose the internally created HttpClient when GraphQLHttpClient gets disposed + _disposeHttpClient = true; + } - public GraphQLHttpClient(GraphQLHttpClientOptions options, IGraphQLWebsocketJsonSerializer serializer, HttpClient httpClient) - { - Options = options ?? throw new ArgumentNullException(nameof(options)); - JsonSerializer = serializer ?? throw new ArgumentNullException(nameof(serializer), "please configure the JSON serializer you want to use"); - HttpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); + public GraphQLHttpClient(GraphQLHttpClientOptions options, IGraphQLWebsocketJsonSerializer serializer, HttpClient httpClient) + { + Options = options ?? throw new ArgumentNullException(nameof(options)); + JsonSerializer = serializer ?? throw new ArgumentNullException(nameof(serializer), "please configure the JSON serializer you want to use"); + HttpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); - if (!HttpClient.DefaultRequestHeaders.UserAgent.Any()) - HttpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(GetType().Assembly.GetName().Name, GetType().Assembly.GetName().Version.ToString())); + if (!HttpClient.DefaultRequestHeaders.UserAgent.Any()) + HttpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(GetType().Assembly.GetName().Name, GetType().Assembly.GetName().Version.ToString())); - _lazyHttpWebSocket = new Lazy(CreateGraphQLHttpWebSocket); - } + _lazyHttpWebSocket = new Lazy(CreateGraphQLHttpWebSocket); + } - #endregion + #endregion - #region IGraphQLClient + #region IGraphQLClient - /// - public async Task> SendQueryAsync(GraphQLRequest request, CancellationToken cancellationToken = default) - { - if (Options.UseWebSocketForQueriesAndMutations || - !(Options.WebSocketEndPoint is null) && Options.EndPoint is null || - Options.EndPoint.HasWebSocketScheme()) - return await GraphQlHttpWebSocket.SendRequest(request, cancellationToken).ConfigureAwait(false); + /// + public async Task> SendQueryAsync(GraphQLRequest request, CancellationToken cancellationToken = default) + { + if (Options.UseWebSocketForQueriesAndMutations || + !(Options.WebSocketEndPoint is null) && Options.EndPoint is null || + Options.EndPoint.HasWebSocketScheme()) + return await GraphQlHttpWebSocket.SendRequest(request, cancellationToken).ConfigureAwait(false); - return await SendHttpRequestAsync(request, cancellationToken).ConfigureAwait(false); - } + return await SendHttpRequestAsync(request, cancellationToken).ConfigureAwait(false); + } - /// - public Task> SendMutationAsync(GraphQLRequest request, - CancellationToken cancellationToken = default) - => SendQueryAsync(request, cancellationToken); + /// + public Task> SendMutationAsync(GraphQLRequest request, + CancellationToken cancellationToken = default) + => SendQueryAsync(request, cancellationToken); - /// - public IObservable> CreateSubscriptionStream(GraphQLRequest request) - => CreateSubscriptionStream(request, null); + /// + public IObservable> CreateSubscriptionStream(GraphQLRequest request) + => CreateSubscriptionStream(request, null); - /// - public IObservable> CreateSubscriptionStream(GraphQLRequest request, Action? exceptionHandler) - { - if (_disposed) - throw new ObjectDisposedException(nameof(GraphQLHttpClient)); + /// + public IObservable> CreateSubscriptionStream(GraphQLRequest request, Action? exceptionHandler) + { + if (_disposed) + throw new ObjectDisposedException(nameof(GraphQLHttpClient)); - var observable = GraphQlHttpWebSocket.CreateSubscriptionStream(request, exceptionHandler); - return observable; - } + var observable = GraphQlHttpWebSocket.CreateSubscriptionStream(request, exceptionHandler); + return observable; + } - #endregion + #endregion - /// - /// Explicitly opens the websocket connection. Will be closed again on disposing the last subscription. - /// - /// - public Task InitializeWebsocketConnection() => GraphQlHttpWebSocket.InitializeWebSocket(); + /// + /// Explicitly opens the websocket connection. Will be closed again on disposing the last subscription. + /// + /// + public Task InitializeWebsocketConnection() => GraphQlHttpWebSocket.InitializeWebSocket(); - #region Private Methods + #region Private Methods - private async Task> SendHttpRequestAsync(GraphQLRequest request, CancellationToken cancellationToken = default) - { - var preprocessedRequest = await Options.PreprocessRequest(request, this).ConfigureAwait(false); + private async Task> SendHttpRequestAsync(GraphQLRequest request, CancellationToken cancellationToken = default) + { + var preprocessedRequest = await Options.PreprocessRequest(request, this).ConfigureAwait(false); - using var httpRequestMessage = preprocessedRequest.ToHttpRequestMessage(Options, JsonSerializer); - using var httpResponseMessage = await HttpClient.SendAsync(httpRequestMessage, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + using var httpRequestMessage = preprocessedRequest.ToHttpRequestMessage(Options, JsonSerializer); + using var httpResponseMessage = await HttpClient.SendAsync(httpRequestMessage, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); - var contentStream = await httpResponseMessage.Content.ReadAsStreamAsync().ConfigureAwait(false); + var contentStream = await httpResponseMessage.Content.ReadAsStreamAsync().ConfigureAwait(false); - if (Options.IsValidResponseToDeserialize(httpResponseMessage)) - { - var graphQLResponse = await JsonSerializer.DeserializeFromUtf8StreamAsync(contentStream, cancellationToken).ConfigureAwait(false); - return graphQLResponse.ToGraphQLHttpResponse(httpResponseMessage.Headers, httpResponseMessage.StatusCode); - } + if (Options.IsValidResponseToDeserialize(httpResponseMessage)) + { + var graphQLResponse = await JsonSerializer.DeserializeFromUtf8StreamAsync(contentStream, cancellationToken).ConfigureAwait(false); + return graphQLResponse.ToGraphQLHttpResponse(httpResponseMessage.Headers, httpResponseMessage.StatusCode); + } - // error handling - string content = null; - if (contentStream != null) - using (var sr = new StreamReader(contentStream)) - content = await sr.ReadToEndAsync().ConfigureAwait(false); + // error handling + string content = null; + if (contentStream != null) + using (var sr = new StreamReader(contentStream)) + content = await sr.ReadToEndAsync().ConfigureAwait(false); - throw new GraphQLHttpRequestException(httpResponseMessage.StatusCode, httpResponseMessage.Headers, content); - } + throw new GraphQLHttpRequestException(httpResponseMessage.StatusCode, httpResponseMessage.Headers, content); + } - private GraphQLHttpWebSocket CreateGraphQLHttpWebSocket() - { - if(Options.WebSocketEndPoint is null && Options.EndPoint is null) - throw new InvalidOperationException("no endpoint configured"); + private GraphQLHttpWebSocket CreateGraphQLHttpWebSocket() + { + if(Options.WebSocketEndPoint is null && Options.EndPoint is null) + throw new InvalidOperationException("no endpoint configured"); - var webSocketEndpoint = Options.WebSocketEndPoint ?? Options.EndPoint.GetWebSocketUri(); - if (!webSocketEndpoint.HasWebSocketScheme()) - throw new InvalidOperationException($"uri \"{webSocketEndpoint}\" is not a websocket endpoint"); + var webSocketEndpoint = Options.WebSocketEndPoint ?? Options.EndPoint.GetWebSocketUri(); + if (!webSocketEndpoint.HasWebSocketScheme()) + throw new InvalidOperationException($"uri \"{webSocketEndpoint}\" is not a websocket endpoint"); - return new GraphQLHttpWebSocket(webSocketEndpoint, this); - } + return new GraphQLHttpWebSocket(webSocketEndpoint, this); + } - #endregion + #endregion - #region IDisposable + #region IDisposable - /// - /// Releases unmanaged resources - /// - public void Dispose() + /// + /// Releases unmanaged resources + /// + public void Dispose() + { + lock (_disposeLocker) { - lock (_disposeLocker) + if (!_disposed) { - if (!_disposed) - { - _disposed = true; - Dispose(true); - } + _disposed = true; + Dispose(true); } } + } - private volatile bool _disposed; - private readonly object _disposeLocker = new(); + private volatile bool _disposed; + private readonly object _disposeLocker = new(); - protected virtual void Dispose(bool disposing) + protected virtual void Dispose(bool disposing) + { + if (disposing) { - if (disposing) - { - Debug.WriteLine($"Disposing GraphQLHttpClient on endpoint {Options.EndPoint}"); - _cancellationTokenSource.Cancel(); - if(_disposeHttpClient) - HttpClient.Dispose(); - if ( _lazyHttpWebSocket.IsValueCreated ) - _lazyHttpWebSocket.Value.Dispose(); - _cancellationTokenSource.Dispose(); - } + Debug.WriteLine($"Disposing GraphQLHttpClient on endpoint {Options.EndPoint}"); + _cancellationTokenSource.Cancel(); + if(_disposeHttpClient) + HttpClient.Dispose(); + if ( _lazyHttpWebSocket.IsValueCreated ) + _lazyHttpWebSocket.Value.Dispose(); + _cancellationTokenSource.Dispose(); } - - #endregion } + + #endregion } diff --git a/src/GraphQL.Client/GraphQLHttpClientExtensions.cs b/src/GraphQL.Client/GraphQLHttpClientExtensions.cs index c3f49dfb..1da01413 100644 --- a/src/GraphQL.Client/GraphQLHttpClientExtensions.cs +++ b/src/GraphQL.Client/GraphQLHttpClientExtensions.cs @@ -1,45 +1,44 @@ using System.Net.WebSockets; using GraphQL.Client.Abstractions; -namespace GraphQL.Client.Http -{ - public static class GraphQLHttpClientExtensions - { - /// - /// Creates a subscription to a GraphQL server. The connection is not established until the first actual subscription is made.
- /// All subscriptions made to this stream share the same hot observable.
- /// All s are passed to the to be handled externally.
- /// If the completes normally, the subscription is recreated with a new connection attempt.
- /// Other s or any exception thrown by will cause the sequence to fail. - ///
- /// the GraphQL client - /// the GraphQL request for this subscription - /// an external handler for all s occurring within the sequence - /// an observable stream for the specified subscription - public static IObservable> CreateSubscriptionStream(this IGraphQLClient client, - GraphQLRequest request, Action webSocketExceptionHandler) => - client.CreateSubscriptionStream(request, e => - { - if (e is WebSocketException webSocketException) - webSocketExceptionHandler(webSocketException); - else - throw e; - }); +namespace GraphQL.Client.Http; - /// - public static IObservable> CreateSubscriptionStream( - this IGraphQLClient client, GraphQLRequest request, Func defineResponseType, Action webSocketExceptionHandler) +public static class GraphQLHttpClientExtensions +{ + /// + /// Creates a subscription to a GraphQL server. The connection is not established until the first actual subscription is made.
+ /// All subscriptions made to this stream share the same hot observable.
+ /// All s are passed to the to be handled externally.
+ /// If the completes normally, the subscription is recreated with a new connection attempt.
+ /// Other s or any exception thrown by will cause the sequence to fail. + ///
+ /// the GraphQL client + /// the GraphQL request for this subscription + /// an external handler for all s occurring within the sequence + /// an observable stream for the specified subscription + public static IObservable> CreateSubscriptionStream(this IGraphQLClient client, + GraphQLRequest request, Action webSocketExceptionHandler) => + client.CreateSubscriptionStream(request, e => { - _ = defineResponseType; - return client.CreateSubscriptionStream(request, webSocketExceptionHandler); - } + if (e is WebSocketException webSocketException) + webSocketExceptionHandler(webSocketException); + else + throw e; + }); - /// - public static IObservable> CreateSubscriptionStream( - this IGraphQLClient client, GraphQLRequest request, Func defineResponseType) - { - _ = defineResponseType; - return client.CreateSubscriptionStream(request); - } + /// + public static IObservable> CreateSubscriptionStream( + this IGraphQLClient client, GraphQLRequest request, Func defineResponseType, Action webSocketExceptionHandler) + { + _ = defineResponseType; + return client.CreateSubscriptionStream(request, webSocketExceptionHandler); + } + + /// + public static IObservable> CreateSubscriptionStream( + this IGraphQLClient client, GraphQLRequest request, Func defineResponseType) + { + _ = defineResponseType; + return client.CreateSubscriptionStream(request); } } diff --git a/src/GraphQL.Client/GraphQLHttpClientOptions.cs b/src/GraphQL.Client/GraphQLHttpClientOptions.cs index acbdfaab..12fad5e3 100644 --- a/src/GraphQL.Client/GraphQLHttpClientOptions.cs +++ b/src/GraphQL.Client/GraphQLHttpClientOptions.cs @@ -2,73 +2,72 @@ using System.Net.Http.Headers; using System.Net.WebSockets; -namespace GraphQL.Client.Http +namespace GraphQL.Client.Http; + +/// +/// The Options that the will use. +/// +public class GraphQLHttpClientOptions { /// - /// The Options that the will use. + /// The GraphQL EndPoint to be used /// - public class GraphQLHttpClientOptions - { - /// - /// The GraphQL EndPoint to be used - /// - public Uri? EndPoint { get; set; } + public Uri? EndPoint { get; set; } - /// - /// The GraphQL EndPoint to be used for websocket connections - /// - public Uri? WebSocketEndPoint { get; set; } + /// + /// The GraphQL EndPoint to be used for websocket connections + /// + public Uri? WebSocketEndPoint { get; set; } - /// - /// The that is going to be used - /// - public HttpMessageHandler HttpMessageHandler { get; set; } = new HttpClientHandler(); + /// + /// The that is going to be used + /// + public HttpMessageHandler HttpMessageHandler { get; set; } = new HttpClientHandler(); - /// - /// The that will be send on POST - /// - public string MediaType { get; set; } = "application/json"; // This should be "application/graphql" also "application/x-www-form-urlencoded" is Accepted + /// + /// The that will be send on POST + /// + public string MediaType { get; set; } = "application/json"; // This should be "application/graphql" also "application/x-www-form-urlencoded" is Accepted - /// - /// The back-off strategy for automatic websocket/subscription reconnects. Calculates the delay before the next connection attempt is made.
- /// default formula: min(n, 5) * 1,5 * random(0.0, 1.0) - ///
- public Func BackOffStrategy { get; set; } = n => - { - var rnd = new Random(); - return TimeSpan.FromSeconds(Math.Min(n, 5) * 1.5 + rnd.NextDouble()); - }; + /// + /// The back-off strategy for automatic websocket/subscription reconnects. Calculates the delay before the next connection attempt is made.
+ /// default formula: min(n, 5) * 1,5 * random(0.0, 1.0) + ///
+ public Func BackOffStrategy { get; set; } = n => + { + 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; + /// + /// If , the websocket connection is also used for regular queries and mutations + /// + public bool UseWebSocketForQueriesAndMutations { get; set; } = false; - /// - /// Request preprocessing function. Can be used i.e. to inject authorization info into a GraphQL request payload. - /// - public Func> PreprocessRequest { get; set; } = (request, client) => - Task.FromResult(request is GraphQLHttpRequest graphQLHttpRequest ? graphQLHttpRequest : new GraphQLHttpRequest(request)); + /// + /// Request preprocessing function. Can be used i.e. to inject authorization info into a GraphQL request payload. + /// + public Func> PreprocessRequest { get; set; } = (request, client) => + Task.FromResult(request is GraphQLHttpRequest graphQLHttpRequest ? graphQLHttpRequest : new GraphQLHttpRequest(request)); - /// - /// Delegate to determine if GraphQL response may be properly deserialized into . - /// - public Func IsValidResponseToDeserialize { get; set; } = r => r.IsSuccessStatusCode || r.StatusCode == HttpStatusCode.BadRequest; + /// + /// Delegate to determine if GraphQL response may be properly deserialized into . + /// + public Func IsValidResponseToDeserialize { get; set; } = r => r.IsSuccessStatusCode || r.StatusCode == HttpStatusCode.BadRequest; - /// - /// This callback is called after successfully establishing a websocket connection but before any regular request is made. - /// - public Func OnWebsocketConnected { get; set; } = client => Task.CompletedTask; + /// + /// This callback is called after successfully establishing a websocket connection but before any regular request is made. + /// + public Func OnWebsocketConnected { get; set; } = client => Task.CompletedTask; - /// - /// Configure additional websocket options (i.e. headers). This will not be invoked on Windows 7 when targeting .NET Framework 4.x. - /// - public Action ConfigureWebsocketOptions { get; set; } = options => { }; + /// + /// Configure additional websocket options (i.e. headers). This will not be invoked on Windows 7 when targeting .NET Framework 4.x. + /// + public Action ConfigureWebsocketOptions { get; set; } = options => { }; - /// - /// Sets the `ConnectionParams` object sent with the GQL_CONNECTION_INIT message on establishing a GraphQL websocket connection. - /// See https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md#gql_connection_init. - /// - public Func ConfigureWebSocketConnectionInitPayload { get; set; } = options => null; - } + /// + /// Sets the `ConnectionParams` object sent with the GQL_CONNECTION_INIT message on establishing a GraphQL websocket connection. + /// See https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md#gql_connection_init. + /// + public Func ConfigureWebSocketConnectionInitPayload { get; set; } = options => null; } diff --git a/src/GraphQL.Client/GraphQLHttpRequest.cs b/src/GraphQL.Client/GraphQLHttpRequest.cs index ac62c6a8..b0591e57 100644 --- a/src/GraphQL.Client/GraphQLHttpRequest.cs +++ b/src/GraphQL.Client/GraphQLHttpRequest.cs @@ -2,47 +2,46 @@ using System.Text; using GraphQL.Client.Abstractions; -namespace GraphQL.Client.Http +namespace GraphQL.Client.Http; + +public class GraphQLHttpRequest : GraphQLRequest { - public class GraphQLHttpRequest : GraphQLRequest + public GraphQLHttpRequest() { - public GraphQLHttpRequest() - { - } + } - public GraphQLHttpRequest(string query, object? variables = null, string? operationName = null) : base(query, variables, operationName) - { - } + public GraphQLHttpRequest(string query, object? variables = null, string? operationName = null) : base(query, variables, operationName) + { + } - public GraphQLHttpRequest(GraphQLRequest other): base(other) - { - } + public GraphQLHttpRequest(GraphQLRequest other): base(other) + { + } - /// - /// Allows to preprocess a before it is sent, i.e. add custom headers - /// - [IgnoreDataMember] - [Obsolete("Inherit from GraphQLHttpRequest and override ToHttpRequestMessage() to customize the HttpRequestMessage. Will be removed in v4.0.0.")] - public Action PreprocessHttpRequestMessage { get; set; } = message => { }; + /// + /// Allows to preprocess a before it is sent, i.e. add custom headers + /// + [IgnoreDataMember] + [Obsolete("Inherit from GraphQLHttpRequest and override ToHttpRequestMessage() to customize the HttpRequestMessage. Will be removed in v4.0.0.")] + public Action PreprocessHttpRequestMessage { get; set; } = message => { }; - /// - /// Creates a from this . - /// Used by to convert GraphQL requests when sending them as regular HTTP requests. - /// - /// the passed from - /// the passed from - /// - public virtual HttpRequestMessage ToHttpRequestMessage(GraphQLHttpClientOptions options, IGraphQLJsonSerializer serializer) + /// + /// Creates a from this . + /// Used by to convert GraphQL requests when sending them as regular HTTP requests. + /// + /// the passed from + /// the passed from + /// + public virtual HttpRequestMessage ToHttpRequestMessage(GraphQLHttpClientOptions options, IGraphQLJsonSerializer serializer) + { + var message = new HttpRequestMessage(HttpMethod.Post, options.EndPoint) { - var message = new HttpRequestMessage(HttpMethod.Post, options.EndPoint) - { - Content = new StringContent(serializer.SerializeToString(this), Encoding.UTF8, options.MediaType) - }; + Content = new StringContent(serializer.SerializeToString(this), Encoding.UTF8, options.MediaType) + }; #pragma warning disable CS0618 // Type or member is obsolete - PreprocessHttpRequestMessage(message); + PreprocessHttpRequestMessage(message); #pragma warning restore CS0618 // Type or member is obsolete - return message; - } + return message; } } diff --git a/src/GraphQL.Client/GraphQLHttpRequestException.cs b/src/GraphQL.Client/GraphQLHttpRequestException.cs index faf7e6c5..808f1417 100644 --- a/src/GraphQL.Client/GraphQLHttpRequestException.cs +++ b/src/GraphQL.Client/GraphQLHttpRequestException.cs @@ -1,40 +1,38 @@ using System.Net; using System.Net.Http.Headers; -namespace GraphQL.Client.Http -{ +namespace GraphQL.Client.Http; +/// +/// An exception thrown on unexpected +/// +public class GraphQLHttpRequestException : Exception +{ /// - /// An exception thrown on unexpected + /// The returned status code /// - public class GraphQLHttpRequestException : Exception - { - /// - /// The returned status code - /// - public HttpStatusCode StatusCode { get; } + public HttpStatusCode StatusCode { get; } - /// - /// the returned response headers - /// - public HttpResponseHeaders ResponseHeaders { get; } + /// + /// the returned response headers + /// + public HttpResponseHeaders ResponseHeaders { get; } - /// - /// the returned content - /// - public string? Content { get; } + /// + /// the returned content + /// + public string? Content { get; } - /// - /// Creates a new instance of - /// - /// - /// - /// - public GraphQLHttpRequestException(HttpStatusCode statusCode, HttpResponseHeaders responseHeaders, string? content) : base($"The HTTP request failed with status code {statusCode}") - { - StatusCode = statusCode; - ResponseHeaders = responseHeaders; - Content = content; - } + /// + /// Creates a new instance of + /// + /// + /// + /// + public GraphQLHttpRequestException(HttpStatusCode statusCode, HttpResponseHeaders responseHeaders, string? content) : base($"The HTTP request failed with status code {statusCode}") + { + StatusCode = statusCode; + ResponseHeaders = responseHeaders; + Content = content; } } diff --git a/src/GraphQL.Client/GraphQLHttpResponse.cs b/src/GraphQL.Client/GraphQLHttpResponse.cs index 8888ff61..cebada2e 100644 --- a/src/GraphQL.Client/GraphQLHttpResponse.cs +++ b/src/GraphQL.Client/GraphQLHttpResponse.cs @@ -1,35 +1,34 @@ using System.Net; using System.Net.Http.Headers; -namespace GraphQL.Client.Http +namespace GraphQL.Client.Http; + +public class GraphQLHttpResponse : GraphQLResponse { - public class GraphQLHttpResponse : GraphQLResponse + public GraphQLHttpResponse(GraphQLResponse response, HttpResponseHeaders responseHeaders, HttpStatusCode statusCode) { - public GraphQLHttpResponse(GraphQLResponse response, HttpResponseHeaders responseHeaders, HttpStatusCode statusCode) - { - Data = response.Data; - Errors = response.Errors; - Extensions = response.Extensions; - ResponseHeaders = responseHeaders; - StatusCode = statusCode; - } + Data = response.Data; + Errors = response.Errors; + Extensions = response.Extensions; + ResponseHeaders = responseHeaders; + StatusCode = statusCode; + } - public HttpResponseHeaders ResponseHeaders { get; set; } + public HttpResponseHeaders ResponseHeaders { get; set; } - public HttpStatusCode StatusCode { get; set; } - } + public HttpStatusCode StatusCode { get; set; } +} - public static class GraphQLResponseExtensions - { - public static GraphQLHttpResponse ToGraphQLHttpResponse(this GraphQLResponse response, HttpResponseHeaders responseHeaders, HttpStatusCode statusCode) => new GraphQLHttpResponse(response, responseHeaders, statusCode); +public static class GraphQLResponseExtensions +{ + public static GraphQLHttpResponse ToGraphQLHttpResponse(this GraphQLResponse response, HttpResponseHeaders responseHeaders, HttpStatusCode statusCode) => new GraphQLHttpResponse(response, responseHeaders, statusCode); - /// - /// Casts to . Throws if the cast fails. - /// - /// - /// - /// is not a - /// - public static GraphQLHttpResponse AsGraphQLHttpResponse(this GraphQLResponse response) => (GraphQLHttpResponse)response; - } + /// + /// Casts to . Throws if the cast fails. + /// + /// + /// + /// is not a + /// + public static GraphQLHttpResponse AsGraphQLHttpResponse(this GraphQLResponse response) => (GraphQLHttpResponse)response; } diff --git a/src/GraphQL.Client/GraphQLSubscriptionException.cs b/src/GraphQL.Client/GraphQLSubscriptionException.cs index 28e8566c..ccaab426 100644 --- a/src/GraphQL.Client/GraphQLSubscriptionException.cs +++ b/src/GraphQL.Client/GraphQLSubscriptionException.cs @@ -1,29 +1,28 @@ using System.Runtime.Serialization; -namespace GraphQL.Client.Http +namespace GraphQL.Client.Http; + +[Serializable] +public class GraphQLSubscriptionException : Exception { - [Serializable] - public class GraphQLSubscriptionException : Exception - { - // - // For guidelines regarding the creation of new exception types, see - // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp - // and - // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp - // + // + // For guidelines regarding the creation of new exception types, see + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp + // and + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp + // - public GraphQLSubscriptionException() - { - } + public GraphQLSubscriptionException() + { + } - public GraphQLSubscriptionException(object error) : base(error.ToString()) - { - } + public GraphQLSubscriptionException(object error) : base(error.ToString()) + { + } - protected GraphQLSubscriptionException( - SerializationInfo info, - StreamingContext context) : base(info, context) - { - } + protected GraphQLSubscriptionException( + SerializationInfo info, + StreamingContext context) : base(info, context) + { } } diff --git a/src/GraphQL.Client/UriExtensions.cs b/src/GraphQL.Client/UriExtensions.cs index 1e2e53e2..47f5841c 100644 --- a/src/GraphQL.Client/UriExtensions.cs +++ b/src/GraphQL.Client/UriExtensions.cs @@ -1,39 +1,38 @@ -namespace GraphQL.Client.Http +namespace GraphQL.Client.Http; + +public static class UriExtensions { - public static class UriExtensions - { - /// - /// Returns true if equals "wss" or "ws" - /// - /// - /// - public static bool HasWebSocketScheme(this Uri? uri) => - !(uri is null) && - (uri.Scheme.Equals("wss", StringComparison.OrdinalIgnoreCase) || uri.Scheme.Equals("ws", StringComparison.OrdinalIgnoreCase)); + /// + /// Returns true if equals "wss" or "ws" + /// + /// + /// + public static bool HasWebSocketScheme(this Uri? uri) => + !(uri is null) && + (uri.Scheme.Equals("wss", StringComparison.OrdinalIgnoreCase) || uri.Scheme.Equals("ws", StringComparison.OrdinalIgnoreCase)); - /// - /// Infers the websocket uri from . - /// - /// - /// - public static Uri GetWebSocketUri(this Uri uri) - { - if (uri is null) - throw new ArgumentNullException(nameof(uri)); + /// + /// Infers the websocket uri from . + /// + /// + /// + public static Uri GetWebSocketUri(this Uri uri) + { + if (uri is null) + throw new ArgumentNullException(nameof(uri)); - if (uri.HasWebSocketScheme()) - return uri; + if (uri.HasWebSocketScheme()) + return uri; - string webSocketScheme; + string webSocketScheme; - if (uri.Scheme == Uri.UriSchemeHttps) - webSocketScheme = "wss"; - else if (uri.Scheme == Uri.UriSchemeHttp) - webSocketScheme = "ws"; - else - throw new NotSupportedException($"cannot infer websocket uri from uri scheme {uri.Scheme}"); + if (uri.Scheme == Uri.UriSchemeHttps) + webSocketScheme = "wss"; + else if (uri.Scheme == Uri.UriSchemeHttp) + webSocketScheme = "ws"; + else + throw new NotSupportedException($"cannot infer websocket uri from uri scheme {uri.Scheme}"); - return new UriBuilder(uri){Scheme = webSocketScheme}.Uri; - } + return new UriBuilder(uri){Scheme = webSocketScheme}.Uri; } } diff --git a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs index 0aa60818..28b721eb 100644 --- a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs +++ b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs @@ -8,370 +8,370 @@ using System.Text; using GraphQL.Client.Abstractions.Websocket; -namespace GraphQL.Client.Http.Websocket +namespace GraphQL.Client.Http.Websocket; + +internal class GraphQLHttpWebSocket : IDisposable { - internal class GraphQLHttpWebSocket : IDisposable - { - #region Private fields + #region Private fields - private readonly Uri _webSocketUri; - private readonly GraphQLHttpClient _client; - private readonly ArraySegment _buffer; - private readonly CancellationTokenSource _internalCancellationTokenSource = new CancellationTokenSource(); - private readonly CancellationToken _internalCancellationToken; - private readonly Subject _requestSubject = new Subject(); - private readonly Subject _exceptionSubject = new Subject(); - private readonly BehaviorSubject _stateSubject = - new BehaviorSubject(GraphQLWebsocketConnectionState.Disconnected); - private readonly IDisposable _requestSubscription; + private readonly Uri _webSocketUri; + private readonly GraphQLHttpClient _client; + private readonly ArraySegment _buffer; + private readonly CancellationTokenSource _internalCancellationTokenSource = new CancellationTokenSource(); + private readonly CancellationToken _internalCancellationToken; + private readonly Subject _requestSubject = new Subject(); + private readonly Subject _exceptionSubject = new Subject(); + private readonly BehaviorSubject _stateSubject = + new BehaviorSubject(GraphQLWebsocketConnectionState.Disconnected); + private readonly IDisposable _requestSubscription; - private int _connectionAttempt = 0; - private IConnectableObservable _incomingMessages; - private IDisposable _incomingMessagesConnection; - private GraphQLHttpClientOptions Options => _client.Options; + private int _connectionAttempt = 0; + private IConnectableObservable _incomingMessages; + private IDisposable _incomingMessagesConnection; + private GraphQLHttpClientOptions Options => _client.Options; - private Task _initializeWebSocketTask = Task.CompletedTask; - private readonly object _initializeLock = new object(); + private Task _initializeWebSocketTask = Task.CompletedTask; + private readonly object _initializeLock = new object(); #if NETFRAMEWORK private WebSocket _clientWebSocket = null; #else - private ClientWebSocket _clientWebSocket = null; + private ClientWebSocket _clientWebSocket = null; #endif - #endregion + #endregion - #region Public properties + #region Public properties - /// - /// The current websocket state - /// - public WebSocketState WebSocketState => _clientWebSocket?.State ?? WebSocketState.None; + /// + /// The current websocket state + /// + public WebSocketState WebSocketState => _clientWebSocket?.State ?? WebSocketState.None; - /// - /// Publishes all errors which occur within the receive pipeline - /// - public IObservable ReceiveErrors => _exceptionSubject.AsObservable(); + /// + /// Publishes all errors which occur within the receive pipeline + /// + public IObservable ReceiveErrors => _exceptionSubject.AsObservable(); - /// - /// Publishes the connection state of the - /// - public IObservable ConnectionState => _stateSubject.DistinctUntilChanged(); + /// + /// Publishes the connection state of the + /// + public IObservable ConnectionState => _stateSubject.DistinctUntilChanged(); - /// - /// Publishes all messages which are received on the websocket - /// - public IObservable IncomingMessageStream { get; } + /// + /// Publishes all messages which are received on the websocket + /// + public IObservable IncomingMessageStream { get; } - #endregion + #endregion - public GraphQLHttpWebSocket(Uri webSocketUri, GraphQLHttpClient client) - { - _internalCancellationToken = _internalCancellationTokenSource.Token; - _webSocketUri = webSocketUri; - _client = client; - _buffer = new ArraySegment(new byte[8192]); - IncomingMessageStream = GetMessageStream(); - - _requestSubscription = _requestSubject - .Select(request => Observable.FromAsync(() => SendWebSocketRequestAsync(request))) - .Concat() - .Subscribe(); - } + public GraphQLHttpWebSocket(Uri webSocketUri, GraphQLHttpClient client) + { + _internalCancellationToken = _internalCancellationTokenSource.Token; + _webSocketUri = webSocketUri; + _client = client; + _buffer = new ArraySegment(new byte[8192]); + IncomingMessageStream = GetMessageStream(); + + _requestSubscription = _requestSubject + .Select(request => Observable.FromAsync(() => SendWebSocketRequestAsync(request))) + .Concat() + .Subscribe(); + } - #region Send requests - - /// - /// Create a new subscription stream - /// - /// the response type - /// the to start the subscription - /// Optional: exception handler for handling exceptions within the receive pipeline - /// a which represents the subscription - public IObservable> CreateSubscriptionStream(GraphQLRequest request, Action? exceptionHandler = null) => - Observable.Defer(() => - Observable.Create>(async observer => + #region Send requests + + /// + /// Create a new subscription stream + /// + /// the response type + /// the to start the subscription + /// Optional: exception handler for handling exceptions within the receive pipeline + /// a which represents the subscription + public IObservable> CreateSubscriptionStream(GraphQLRequest request, Action? exceptionHandler = null) => + Observable.Defer(() => + Observable.Create>(async observer => + { + Debug.WriteLine($"Create observable thread id: {Thread.CurrentThread.ManagedThreadId}"); + var preprocessedRequest = await _client.Options.PreprocessRequest(request, _client).ConfigureAwait(false); + + var startRequest = new GraphQLWebSocketRequest + { + Id = Guid.NewGuid().ToString("N"), + Type = GraphQLWebSocketMessageType.GQL_START, + Payload = preprocessedRequest + }; + var stopRequest = new GraphQLWebSocketRequest { - Debug.WriteLine($"Create observable thread id: {Thread.CurrentThread.ManagedThreadId}"); - var preprocessedRequest = await _client.Options.PreprocessRequest(request, _client).ConfigureAwait(false); + Id = startRequest.Id, + Type = GraphQLWebSocketMessageType.GQL_STOP + }; - var startRequest = new GraphQLWebSocketRequest - { - Id = Guid.NewGuid().ToString("N"), - Type = GraphQLWebSocketMessageType.GQL_START, - Payload = preprocessedRequest - }; - var stopRequest = new GraphQLWebSocketRequest - { - Id = startRequest.Id, - Type = GraphQLWebSocketMessageType.GQL_STOP - }; - - var observable = Observable.Create>(o => - IncomingMessageStream - // ignore null values and messages for other requests - .Where(response => response != null && response.Id == startRequest.Id) - .Subscribe(response => - { - // terminate the sequence when a 'complete' message is received - if (response.Type == GraphQLWebSocketMessageType.GQL_COMPLETE) - { - Debug.WriteLine($"received 'complete' message on subscription {startRequest.Id}"); - o.OnCompleted(); - return; - } - - // post the GraphQLResponse to the stream (even if a GraphQL error occurred) - Debug.WriteLine($"received payload on subscription {startRequest.Id} (thread {Thread.CurrentThread.ManagedThreadId})"); - var typedResponse = - _client.JsonSerializer.DeserializeToWebsocketResponse( - response.MessageBytes); - Debug.WriteLine($"payload => {System.Text.Encoding.UTF8.GetString(response.MessageBytes)}"); - o.OnNext(typedResponse.Payload); - - // in case of a GraphQL error, terminate the sequence after the response has been posted - if (response.Type == GraphQLWebSocketMessageType.GQL_ERROR) - { - Debug.WriteLine($"terminating subscription {startRequest.Id} because of a GraphQL error"); - o.OnCompleted(); - } - }, - e => + var observable = Observable.Create>(o => + IncomingMessageStream + // ignore null values and messages for other requests + .Where(response => response != null && response.Id == startRequest.Id) + .Subscribe(response => + { + // terminate the sequence when a 'complete' message is received + if (response.Type == GraphQLWebSocketMessageType.GQL_COMPLETE) { - Debug.WriteLine($"response stream for subscription {startRequest.Id} failed: {e}"); - o.OnError(e); - }, - () => + Debug.WriteLine($"received 'complete' message on subscription {startRequest.Id}"); + o.OnCompleted(); + return; + } + + // post the GraphQLResponse to the stream (even if a GraphQL error occurred) + Debug.WriteLine($"received payload on subscription {startRequest.Id} (thread {Thread.CurrentThread.ManagedThreadId})"); + var typedResponse = + _client.JsonSerializer.DeserializeToWebsocketResponse( + response.MessageBytes); + Debug.WriteLine($"payload => {System.Text.Encoding.UTF8.GetString(response.MessageBytes)}"); + o.OnNext(typedResponse.Payload); + + // in case of a GraphQL error, terminate the sequence after the response has been posted + if (response.Type == GraphQLWebSocketMessageType.GQL_ERROR) { - Debug.WriteLine($"response stream for subscription {startRequest.Id} completed"); + Debug.WriteLine($"terminating subscription {startRequest.Id} because of a GraphQL error"); o.OnCompleted(); - }) - ); + } + }, + e => + { + Debug.WriteLine($"response stream for subscription {startRequest.Id} failed: {e}"); + o.OnError(e); + }, + () => + { + Debug.WriteLine($"response stream for subscription {startRequest.Id} completed"); + o.OnCompleted(); + }) + ); - try - { - // initialize websocket (completes immediately if socket is already open) - await InitializeWebSocket().ConfigureAwait(false); - } - catch (Exception e) + try + { + // initialize websocket (completes immediately if socket is already open) + await InitializeWebSocket().ConfigureAwait(false); + } + catch (Exception e) + { + // subscribe observer to failed observable + return Observable.Throw>(e).Subscribe(observer); + } + + var disposable = new CompositeDisposable( + observable.Subscribe(observer), + Disposable.Create(async () => { - // subscribe observer to failed observable - return Observable.Throw>(e).Subscribe(observer); - } + Debug.WriteLine($"disposing subscription {startRequest.Id}, websocket state is '{WebSocketState}'"); + // only try to send close request on open websocket + if (WebSocketState != WebSocketState.Open) + return; - var disposable = new CompositeDisposable( - observable.Subscribe(observer), - Disposable.Create(async () => + try { - Debug.WriteLine($"disposing subscription {startRequest.Id}, websocket state is '{WebSocketState}'"); - // only try to send close request on open websocket - if (WebSocketState != WebSocketState.Open) - return; - - try - { - Debug.WriteLine($"sending stop message on subscription {startRequest.Id}"); - await QueueWebSocketRequest(stopRequest).ConfigureAwait(false); - } - // do not break on disposing - catch (OperationCanceledException) { } - }) - ); - - Debug.WriteLine($"sending start message on subscription {startRequest.Id}"); - // send subscription request - try - { - await QueueWebSocketRequest(startRequest).ConfigureAwait(false); - } - catch (Exception e) - { - Debug.WriteLine(e); - throw; - } - - return disposable; - })) - // complete sequence on OperationCanceledException, this is triggered by the cancellation token - .Catch, OperationCanceledException>(exception => - Observable.Empty>()) - // wrap results - .Select(response => new Tuple, Exception>(response, null)) - // do exception handling - .Catch, Exception>, Exception>(e => - { + Debug.WriteLine($"sending stop message on subscription {startRequest.Id}"); + await QueueWebSocketRequest(stopRequest).ConfigureAwait(false); + } + // do not break on disposing + catch (OperationCanceledException) { } + }) + ); + + Debug.WriteLine($"sending start message on subscription {startRequest.Id}"); + // send subscription request try { - 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 - if (_internalCancellationToken.IsCancellationRequested) - return Observable.Empty, Exception>>(); - else - { - Debug.WriteLine($"Catch handler thread id: {Thread.CurrentThread.ManagedThreadId}"); - return Observable.Throw, Exception>>(e); - } + await QueueWebSocketRequest(startRequest).ConfigureAwait(false); } - catch (Exception exception) + catch (Exception e) { - // wrap all other exceptions to be propagated behind retry - return Observable.Return(new Tuple, Exception>(null, exception)); + Debug.WriteLine(e); + throw; } - }) - // attempt to recreate the websocket for rethrown exceptions - .Retry() - // unwrap and push results or throw wrapped exceptions - .SelectMany(t => + + return disposable; + })) + // complete sequence on OperationCanceledException, this is triggered by the cancellation token + .Catch, OperationCanceledException>(exception => + Observable.Empty>()) + // wrap results + .Select(response => new Tuple, Exception>(response, null)) + // do exception handling + .Catch, Exception>, Exception>(e => + { + try { - // if the result contains an exception, throw it on the observable - if (t.Item2 != null) + if (exceptionHandler == null) { - Debug.WriteLine($"unwrap exception thread id: {Thread.CurrentThread.ManagedThreadId} => {t.Item2}"); - return Observable.Throw>(t.Item2); + // 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; } - if (t.Item1 == null) + else { - Debug.WriteLine($"empty item thread id: {Thread.CurrentThread.ManagedThreadId}"); - return Observable.Empty>(); + // exceptions thrown by the handler will propagate to OnError() + exceptionHandler?.Invoke(e); } - return Observable.Return(t.Item1); - }); - /// - /// Send a regular GraphQL request (query, mutation) via websocket - /// - /// the response type - /// the to send - /// the token to cancel the request - /// - public Task> SendRequest(GraphQLRequest request, CancellationToken cancellationToken = default) => - Observable.Create>(async observer => - { - var preprocessedRequest = await _client.Options.PreprocessRequest(request, _client).ConfigureAwait(false); - var websocketRequest = new GraphQLWebSocketRequest - { - Id = Guid.NewGuid().ToString("N"), - Type = GraphQLWebSocketMessageType.GQL_START, - Payload = preprocessedRequest - }; - var observable = IncomingMessageStream - .Where(response => response != null && response.Id == websocketRequest.Id) - .TakeUntil(response => response.Type == GraphQLWebSocketMessageType.GQL_COMPLETE) - .Select(response => - { - Debug.WriteLine($"received response for request {websocketRequest.Id}"); - var typedResponse = - _client.JsonSerializer.DeserializeToWebsocketResponse( - response.MessageBytes); - return typedResponse.Payload; - }); - try - { - // initialize websocket (completes immediately if socket is already open) - await InitializeWebSocket().ConfigureAwait(false); + // throw exception on the observable to be caught by Retry() or complete sequence if cancellation was requested + if (_internalCancellationToken.IsCancellationRequested) + return Observable.Empty, Exception>>(); + else + { + Debug.WriteLine($"Catch handler thread id: {Thread.CurrentThread.ManagedThreadId}"); + return Observable.Throw, Exception>>(e); + } } - catch (Exception e) + catch (Exception exception) { - // subscribe observer to failed observable - return Observable.Throw>(e).Subscribe(observer); + // wrap all other exceptions to be propagated behind retry + return Observable.Return(new Tuple, Exception>(null, exception)); } - - var disposable = new CompositeDisposable( - observable.Subscribe(observer) - ); - - Debug.WriteLine($"submitting request {websocketRequest.Id}"); - // send request - try + }) + // attempt to recreate the websocket for rethrown exceptions + .Retry() + // unwrap and push results or throw wrapped exceptions + .SelectMany(t => + { + // if the result contains an exception, throw it on the observable + if (t.Item2 != null) { - await QueueWebSocketRequest(websocketRequest).ConfigureAwait(false); + Debug.WriteLine($"unwrap exception thread id: {Thread.CurrentThread.ManagedThreadId} => {t.Item2}"); + return Observable.Throw>(t.Item2); } - catch (Exception e) + if (t.Item1 == null) { - Debug.WriteLine(e); - throw; + Debug.WriteLine($"empty item thread id: {Thread.CurrentThread.ManagedThreadId}"); + return Observable.Empty>(); } - - return disposable; - }) - // complete sequence on OperationCanceledException, this is triggered by the cancellation token - .Catch, OperationCanceledException>(exception => - Observable.Empty>()) - .FirstAsync() - .ToTask(cancellationToken); - - private Task QueueWebSocketRequest(GraphQLWebSocketRequest request) - { - _requestSubject.OnNext(request); - return request.SendTask(); - } - - private async Task SendWebSocketRequestAsync(GraphQLWebSocketRequest request) + return Observable.Return(t.Item1); + }); + /// + /// Send a regular GraphQL request (query, mutation) via websocket + /// + /// the response type + /// the to send + /// the token to cancel the request + /// + public Task> SendRequest(GraphQLRequest request, CancellationToken cancellationToken = default) => + Observable.Create>(async observer => { - try + var preprocessedRequest = await _client.Options.PreprocessRequest(request, _client).ConfigureAwait(false); + var websocketRequest = new GraphQLWebSocketRequest { - if (_internalCancellationToken.IsCancellationRequested) + Id = Guid.NewGuid().ToString("N"), + Type = GraphQLWebSocketMessageType.GQL_START, + Payload = preprocessedRequest + }; + var observable = IncomingMessageStream + .Where(response => response != null && response.Id == websocketRequest.Id) + .TakeUntil(response => response.Type == GraphQLWebSocketMessageType.GQL_COMPLETE) + .Select(response => { - request.SendCanceled(); - return Unit.Default; - } + Debug.WriteLine($"received response for request {websocketRequest.Id}"); + var typedResponse = + _client.JsonSerializer.DeserializeToWebsocketResponse( + response.MessageBytes); + return typedResponse.Payload; + }); + try + { + // initialize websocket (completes immediately if socket is already open) await InitializeWebSocket().ConfigureAwait(false); - await SendWebSocketMessageAsync(request, _internalCancellationToken).ConfigureAwait(false); - request.SendCompleted(); } catch (Exception e) { - request.SendFailed(e); + // subscribe observer to failed observable + return Observable.Throw>(e).Subscribe(observer); } - return Unit.Default; - } - private async Task SendWebSocketMessageAsync(GraphQLWebSocketRequest request, CancellationToken cancellationToken = default) + var disposable = new CompositeDisposable( + observable.Subscribe(observer) + ); + + Debug.WriteLine($"submitting request {websocketRequest.Id}"); + // send request + try + { + await QueueWebSocketRequest(websocketRequest).ConfigureAwait(false); + } + catch (Exception e) + { + Debug.WriteLine(e); + throw; + } + + return disposable; + }) + // complete sequence on OperationCanceledException, this is triggered by the cancellation token + .Catch, OperationCanceledException>(exception => + Observable.Empty>()) + .FirstAsync() + .ToTask(cancellationToken); + + private Task QueueWebSocketRequest(GraphQLWebSocketRequest request) + { + _requestSubject.OnNext(request); + return request.SendTask(); + } + + private async Task SendWebSocketRequestAsync(GraphQLWebSocketRequest request) + { + try + { + if (_internalCancellationToken.IsCancellationRequested) + { + request.SendCanceled(); + return Unit.Default; + } + + await InitializeWebSocket().ConfigureAwait(false); + await SendWebSocketMessageAsync(request, _internalCancellationToken).ConfigureAwait(false); + request.SendCompleted(); + } + catch (Exception e) { - var requestBytes = _client.JsonSerializer.SerializeToBytes(request); - await _clientWebSocket.SendAsync( - new ArraySegment(requestBytes), - WebSocketMessageType.Text, - true, - cancellationToken).ConfigureAwait(false); + request.SendFailed(e); } + return Unit.Default; + } - #endregion + private async Task SendWebSocketMessageAsync(GraphQLWebSocketRequest request, CancellationToken cancellationToken = default) + { + var requestBytes = _client.JsonSerializer.SerializeToBytes(request); + await _clientWebSocket.SendAsync( + new ArraySegment(requestBytes), + WebSocketMessageType.Text, + true, + cancellationToken).ConfigureAwait(false); + } - public Task InitializeWebSocket() - { - // do not attempt to initialize if cancellation is requested - if (Completion != null) - throw new OperationCanceledException(); + #endregion - lock (_initializeLock) - { - // if an initialization task is already running, return that - if (_initializeWebSocketTask != null && - !_initializeWebSocketTask.IsFaulted && - !_initializeWebSocketTask.IsCompleted) - return _initializeWebSocketTask; + public Task InitializeWebSocket() + { + // do not attempt to initialize if cancellation is requested + if (Completion != null) + throw new OperationCanceledException(); - // if the websocket is open, return a completed task - if (_clientWebSocket != null && _clientWebSocket.State == WebSocketState.Open) - return Task.CompletedTask; + lock (_initializeLock) + { + // if an initialization task is already running, return that + if (_initializeWebSocketTask != null && + !_initializeWebSocketTask.IsFaulted && + !_initializeWebSocketTask.IsCompleted) + return _initializeWebSocketTask; + + // if the websocket is open, return a completed task + if (_clientWebSocket != null && _clientWebSocket.State == WebSocketState.Open) + return Task.CompletedTask; - // else (re-)create websocket and connect - _clientWebSocket?.Dispose(); + // else (re-)create websocket and connect + _clientWebSocket?.Dispose(); #if NETFRAMEWORK // fix websocket not supported on win 7 using @@ -382,310 +382,309 @@ public Task InitializeWebSocket() nativeWebSocket.Options.AddSubProtocol("graphql-ws"); nativeWebSocket.Options.ClientCertificates = ((HttpClientHandler)Options.HttpMessageHandler).ClientCertificates; nativeWebSocket.Options.UseDefaultCredentials = ((HttpClientHandler)Options.HttpMessageHandler).UseDefaultCredentials; - Options.ConfigureWebsocketOptions(nativeWebSocket.Options); - break; + Options.ConfigureWebsocketOptions(nativeWebSocket.Options); + break; case System.Net.WebSockets.Managed.ClientWebSocket managedWebSocket: managedWebSocket.Options.AddSubProtocol("graphql-ws"); managedWebSocket.Options.ClientCertificates = ((HttpClientHandler)Options.HttpMessageHandler).ClientCertificates; managedWebSocket.Options.UseDefaultCredentials = ((HttpClientHandler)Options.HttpMessageHandler).UseDefaultCredentials; - break; + break; default: throw new NotSupportedException($"unknown websocket type {_clientWebSocket.GetType().Name}"); } #else - _clientWebSocket = new ClientWebSocket(); - _clientWebSocket.Options.AddSubProtocol("graphql-ws"); + _clientWebSocket = new ClientWebSocket(); + _clientWebSocket.Options.AddSubProtocol("graphql-ws"); - // the following properties are not supported in Blazor WebAssembly and throw a PlatformNotSupportedException error when accessed - try - { - _clientWebSocket.Options.ClientCertificates = ((HttpClientHandler)Options.HttpMessageHandler).ClientCertificates; - } - catch (NotImplementedException) - { - Debug.WriteLine("property 'ClientWebSocketOptions.ClientCertificates' not implemented by current platform"); - } - catch (PlatformNotSupportedException) - { - Debug.WriteLine("property 'ClientWebSocketOptions.ClientCertificates' not supported by current platform"); - } + // the following properties are not supported in Blazor WebAssembly and throw a PlatformNotSupportedException error when accessed + try + { + _clientWebSocket.Options.ClientCertificates = ((HttpClientHandler)Options.HttpMessageHandler).ClientCertificates; + } + catch (NotImplementedException) + { + Debug.WriteLine("property 'ClientWebSocketOptions.ClientCertificates' not implemented by current platform"); + } + catch (PlatformNotSupportedException) + { + Debug.WriteLine("property 'ClientWebSocketOptions.ClientCertificates' not supported by current platform"); + } - try - { - _clientWebSocket.Options.UseDefaultCredentials = ((HttpClientHandler)Options.HttpMessageHandler).UseDefaultCredentials; - } - catch (NotImplementedException) - { - Debug.WriteLine("property 'ClientWebSocketOptions.UseDefaultCredentials' not implemented by current platform"); - } - catch (PlatformNotSupportedException) - { - Debug.WriteLine("Property 'ClientWebSocketOptions.UseDefaultCredentials' not supported by current platform"); - } + try + { + _clientWebSocket.Options.UseDefaultCredentials = ((HttpClientHandler)Options.HttpMessageHandler).UseDefaultCredentials; + } + catch (NotImplementedException) + { + Debug.WriteLine("property 'ClientWebSocketOptions.UseDefaultCredentials' not implemented by current platform"); + } + catch (PlatformNotSupportedException) + { + Debug.WriteLine("Property 'ClientWebSocketOptions.UseDefaultCredentials' not supported by current platform"); + } - Options.ConfigureWebsocketOptions(_clientWebSocket.Options); + Options.ConfigureWebsocketOptions(_clientWebSocket.Options); #endif - return _initializeWebSocketTask = ConnectAsync(_internalCancellationToken); - } + return _initializeWebSocketTask = ConnectAsync(_internalCancellationToken); } + } - private async Task ConnectAsync(CancellationToken token) + private async Task ConnectAsync(CancellationToken token) + { + try { - try + await BackOff().ConfigureAwait(false); + _stateSubject.OnNext(GraphQLWebsocketConnectionState.Connecting); + Debug.WriteLine($"opening websocket {_clientWebSocket.GetHashCode()} (thread {Thread.CurrentThread.ManagedThreadId})"); + await _clientWebSocket.ConnectAsync(_webSocketUri, token).ConfigureAwait(false); + _stateSubject.OnNext(GraphQLWebsocketConnectionState.Connected); + Debug.WriteLine($"connection established on websocket {_clientWebSocket.GetHashCode()}, invoking Options.OnWebsocketConnected()"); + await (Options.OnWebsocketConnected?.Invoke(_client) ?? Task.CompletedTask).ConfigureAwait(false); + Debug.WriteLine($"invoking Options.OnWebsocketConnected() on websocket {_clientWebSocket.GetHashCode()}"); + _connectionAttempt = 1; + + // create receiving observable + _incomingMessages = Observable + .Defer(() => GetReceiveTask().ToObservable()) + .Repeat() + // complete sequence on OperationCanceledException, this is triggered by the cancellation token on disposal + .Catch(exception => Observable.Empty()) + .Publish(); + + // subscribe maintenance + var maintenanceSubscription = _incomingMessages.Subscribe(_ => { }, ex => { - await BackOff().ConfigureAwait(false); - _stateSubject.OnNext(GraphQLWebsocketConnectionState.Connecting); - Debug.WriteLine($"opening websocket {_clientWebSocket.GetHashCode()} (thread {Thread.CurrentThread.ManagedThreadId})"); - await _clientWebSocket.ConnectAsync(_webSocketUri, token).ConfigureAwait(false); - _stateSubject.OnNext(GraphQLWebsocketConnectionState.Connected); - Debug.WriteLine($"connection established on websocket {_clientWebSocket.GetHashCode()}, invoking Options.OnWebsocketConnected()"); - await (Options.OnWebsocketConnected?.Invoke(_client) ?? Task.CompletedTask).ConfigureAwait(false); - Debug.WriteLine($"invoking Options.OnWebsocketConnected() on websocket {_clientWebSocket.GetHashCode()}"); - _connectionAttempt = 1; - - // create receiving observable - _incomingMessages = Observable - .Defer(() => GetReceiveTask().ToObservable()) - .Repeat() - // complete sequence on OperationCanceledException, this is triggered by the cancellation token on disposal - .Catch(exception => Observable.Empty()) - .Publish(); - - // subscribe maintenance - var maintenanceSubscription = _incomingMessages.Subscribe(_ => { }, ex => + Debug.WriteLine($"incoming message stream {_incomingMessages.GetHashCode()} received an error: {ex}"); + _exceptionSubject.OnNext(ex); + _incomingMessagesConnection?.Dispose(); + _stateSubject.OnNext(GraphQLWebsocketConnectionState.Disconnected); + }, + () => { - Debug.WriteLine($"incoming message stream {_incomingMessages.GetHashCode()} received an error: {ex}"); - _exceptionSubject.OnNext(ex); + Debug.WriteLine($"incoming message stream {_incomingMessages.GetHashCode()} completed"); _incomingMessagesConnection?.Dispose(); _stateSubject.OnNext(GraphQLWebsocketConnectionState.Disconnected); - }, - () => - { - Debug.WriteLine($"incoming message stream {_incomingMessages.GetHashCode()} completed"); - _incomingMessagesConnection?.Dispose(); - _stateSubject.OnNext(GraphQLWebsocketConnectionState.Disconnected); - }); - + }); - // connect observable - var connection = _incomingMessages.Connect(); - Debug.WriteLine($"new incoming message stream {_incomingMessages.GetHashCode()} created"); - _incomingMessagesConnection = new CompositeDisposable(maintenanceSubscription, connection); + // connect observable + var connection = _incomingMessages.Connect(); + Debug.WriteLine($"new incoming message stream {_incomingMessages.GetHashCode()} created"); - var initRequest = new GraphQLWebSocketRequest - { - Type = GraphQLWebSocketMessageType.GQL_CONNECTION_INIT, - Payload = Options.ConfigureWebSocketConnectionInitPayload(Options) - }; + _incomingMessagesConnection = new CompositeDisposable(maintenanceSubscription, connection); - // setup task to await connection_ack message - var ackTask = _incomingMessages - .Where(response => response != null) - .TakeUntil(response => response.Type == GraphQLWebSocketMessageType.GQL_CONNECTION_ACK || - response.Type == GraphQLWebSocketMessageType.GQL_CONNECTION_ERROR) - .LastAsync() - .ToTask(); - - // send connection init - Debug.WriteLine($"sending connection init message"); - await SendWebSocketMessageAsync(initRequest).ConfigureAwait(false); - var response = await ackTask.ConfigureAwait(false); - - if (response.Type == GraphQLWebSocketMessageType.GQL_CONNECTION_ACK) - Debug.WriteLine($"connection acknowledged: {Encoding.UTF8.GetString(response.MessageBytes)}"); - else - { - var errorPayload = Encoding.UTF8.GetString(response.MessageBytes); - Debug.WriteLine($"connection error received: {errorPayload}"); - throw new GraphQLWebsocketConnectionException(errorPayload); - } - } - catch (Exception e) + var initRequest = new GraphQLWebSocketRequest { - Debug.WriteLine($"failed to establish websocket connection"); - _stateSubject.OnNext(GraphQLWebsocketConnectionState.Disconnected); - _exceptionSubject.OnNext(e); - throw; + Type = GraphQLWebSocketMessageType.GQL_CONNECTION_INIT, + Payload = Options.ConfigureWebSocketConnectionInitPayload(Options) + }; + + // setup task to await connection_ack message + var ackTask = _incomingMessages + .Where(response => response != null) + .TakeUntil(response => response.Type == GraphQLWebSocketMessageType.GQL_CONNECTION_ACK || + response.Type == GraphQLWebSocketMessageType.GQL_CONNECTION_ERROR) + .LastAsync() + .ToTask(); + + // send connection init + Debug.WriteLine($"sending connection init message"); + await SendWebSocketMessageAsync(initRequest).ConfigureAwait(false); + var response = await ackTask.ConfigureAwait(false); + + if (response.Type == GraphQLWebSocketMessageType.GQL_CONNECTION_ACK) + Debug.WriteLine($"connection acknowledged: {Encoding.UTF8.GetString(response.MessageBytes)}"); + else + { + var errorPayload = Encoding.UTF8.GetString(response.MessageBytes); + Debug.WriteLine($"connection error received: {errorPayload}"); + throw new GraphQLWebsocketConnectionException(errorPayload); } } - - /// - /// delay the next connection attempt using - /// - /// - private Task BackOff() + catch (Exception e) { - _connectionAttempt++; - - if (_connectionAttempt == 1) - return Task.CompletedTask; - - var delay = Options.BackOffStrategy?.Invoke(_connectionAttempt - 1) ?? TimeSpan.FromSeconds(5); - Debug.WriteLine($"connection attempt #{_connectionAttempt}, backing off for {delay.TotalSeconds} s"); - return Task.Delay(delay, _internalCancellationToken); + Debug.WriteLine($"failed to establish websocket connection"); + _stateSubject.OnNext(GraphQLWebsocketConnectionState.Disconnected); + _exceptionSubject.OnNext(e); + throw; } + } - private IObservable GetMessageStream() => - Observable.Create(async observer => - { - // make sure the websocket is connected - await InitializeWebSocket().ConfigureAwait(false); - // subscribe observer to message stream - var subscription = new CompositeDisposable(_incomingMessages - .Subscribe(observer)) - { - // register the observer's OnCompleted method with the cancellation token to complete the sequence on disposal - _internalCancellationTokenSource.Token.Register(observer.OnCompleted) - }; + /// + /// delay the next connection attempt using + /// + /// + private Task BackOff() + { + _connectionAttempt++; - // add some debug output - var hashCode = subscription.GetHashCode(); - subscription.Add(Disposable.Create(() => Debug.WriteLine($"incoming message subscription {hashCode} disposed"))); - Debug.WriteLine($"new incoming message subscription {hashCode} created"); + if (_connectionAttempt == 1) + return Task.CompletedTask; - return subscription; - }); + var delay = Options.BackOffStrategy?.Invoke(_connectionAttempt - 1) ?? TimeSpan.FromSeconds(5); + Debug.WriteLine($"connection attempt #{_connectionAttempt}, backing off for {delay.TotalSeconds} s"); + return Task.Delay(delay, _internalCancellationToken); + } - private Task _receiveAsyncTask = null; - private readonly object _receiveTaskLocker = new object(); - /// - /// wrapper method to pick up the existing request task if already running - /// - /// - private Task GetReceiveTask() - { - lock (_receiveTaskLocker) + private IObservable GetMessageStream() => + Observable.Create(async observer => { - _internalCancellationToken.ThrowIfCancellationRequested(); - if (_receiveAsyncTask == null || - _receiveAsyncTask.IsFaulted || - _receiveAsyncTask.IsCompleted) - _receiveAsyncTask = ReceiveWebsocketMessagesAsync(); - } + // make sure the websocket is connected + await InitializeWebSocket().ConfigureAwait(false); + // subscribe observer to message stream + var subscription = new CompositeDisposable(_incomingMessages + .Subscribe(observer)) + { + // register the observer's OnCompleted method with the cancellation token to complete the sequence on disposal + _internalCancellationTokenSource.Token.Register(observer.OnCompleted) + }; - return _receiveAsyncTask; + // add some debug output + var hashCode = subscription.GetHashCode(); + subscription.Add(Disposable.Create(() => Debug.WriteLine($"incoming message subscription {hashCode} disposed"))); + Debug.WriteLine($"new incoming message subscription {hashCode} created"); + + return subscription; + }); + + private Task _receiveAsyncTask = null; + private readonly object _receiveTaskLocker = new object(); + /// + /// wrapper method to pick up the existing request task if already running + /// + /// + private Task GetReceiveTask() + { + lock (_receiveTaskLocker) + { + _internalCancellationToken.ThrowIfCancellationRequested(); + if (_receiveAsyncTask == null || + _receiveAsyncTask.IsFaulted || + _receiveAsyncTask.IsCompleted) + _receiveAsyncTask = ReceiveWebsocketMessagesAsync(); } - /// - /// read a single message from the websocket - /// - /// - private async Task ReceiveWebsocketMessagesAsync() + return _receiveAsyncTask; + } + + /// + /// read a single message from the websocket + /// + /// + private async Task ReceiveWebsocketMessagesAsync() + { + _internalCancellationToken.ThrowIfCancellationRequested(); + + try { - _internalCancellationToken.ThrowIfCancellationRequested(); + Debug.WriteLine($"waiting for data on websocket {_clientWebSocket.GetHashCode()} (thread {Thread.CurrentThread.ManagedThreadId})..."); - try + using var ms = new MemoryStream(); + WebSocketReceiveResult webSocketReceiveResult = null; + do { - Debug.WriteLine($"waiting for data on websocket {_clientWebSocket.GetHashCode()} (thread {Thread.CurrentThread.ManagedThreadId})..."); - - using var ms = new MemoryStream(); - WebSocketReceiveResult webSocketReceiveResult = null; - do - { - // cancellation is done implicitly via the close method - webSocketReceiveResult = await _clientWebSocket.ReceiveAsync(_buffer, CancellationToken.None).ConfigureAwait(false); - ms.Write(_buffer.Array, _buffer.Offset, webSocketReceiveResult.Count); - } - while (!webSocketReceiveResult.EndOfMessage && !_internalCancellationToken.IsCancellationRequested); + // cancellation is done implicitly via the close method + webSocketReceiveResult = await _clientWebSocket.ReceiveAsync(_buffer, CancellationToken.None).ConfigureAwait(false); + ms.Write(_buffer.Array, _buffer.Offset, webSocketReceiveResult.Count); + } + while (!webSocketReceiveResult.EndOfMessage && !_internalCancellationToken.IsCancellationRequested); - _internalCancellationToken.ThrowIfCancellationRequested(); - ms.Seek(0, SeekOrigin.Begin); + _internalCancellationToken.ThrowIfCancellationRequested(); + ms.Seek(0, SeekOrigin.Begin); - switch (webSocketReceiveResult.MessageType) - { - case WebSocketMessageType.Text: - var response = await _client.JsonSerializer.DeserializeToWebsocketResponseWrapperAsync(ms).ConfigureAwait(false); - response.MessageBytes = ms.ToArray(); - Debug.WriteLine($"{response.MessageBytes.Length} bytes received for id {response.Id} on websocket {_clientWebSocket.GetHashCode()} (thread {Thread.CurrentThread.ManagedThreadId})..."); - return response; + switch (webSocketReceiveResult.MessageType) + { + case WebSocketMessageType.Text: + var response = await _client.JsonSerializer.DeserializeToWebsocketResponseWrapperAsync(ms).ConfigureAwait(false); + response.MessageBytes = ms.ToArray(); + Debug.WriteLine($"{response.MessageBytes.Length} bytes received for id {response.Id} on websocket {_clientWebSocket.GetHashCode()} (thread {Thread.CurrentThread.ManagedThreadId})..."); + return response; - case WebSocketMessageType.Close: - var closeResponse = await _client.JsonSerializer.DeserializeToWebsocketResponseWrapperAsync(ms).ConfigureAwait(false); - closeResponse.MessageBytes = ms.ToArray(); - Debug.WriteLine($"Connection closed by the server."); - throw new Exception("Connection closed by the server."); + case WebSocketMessageType.Close: + var closeResponse = await _client.JsonSerializer.DeserializeToWebsocketResponseWrapperAsync(ms).ConfigureAwait(false); + closeResponse.MessageBytes = ms.ToArray(); + Debug.WriteLine($"Connection closed by the server."); + throw new Exception("Connection closed by the server."); - default: - throw new NotSupportedException($"Websocket message type {webSocketReceiveResult.MessageType} not supported."); + default: + throw new NotSupportedException($"Websocket message type {webSocketReceiveResult.MessageType} not supported."); - } - } - catch (Exception e) - { - Debug.WriteLine($"exception thrown while receiving websocket data: {e}"); - throw; } } - - private async Task CloseAsync() + catch (Exception e) { - if (_clientWebSocket == null) - return; - - // don't attempt to close the websocket if it is in a failed state - if (_clientWebSocket.State != WebSocketState.Open && - _clientWebSocket.State != WebSocketState.CloseReceived && - _clientWebSocket.State != WebSocketState.CloseSent) - { - Debug.WriteLine($"websocket {_clientWebSocket.GetHashCode()} state = {_clientWebSocket.State}"); - return; - } + Debug.WriteLine($"exception thrown while receiving websocket data: {e}"); + throw; + } + } - Debug.WriteLine($"send \"connection_terminate\" message"); - await SendWebSocketMessageAsync(new GraphQLWebSocketRequest { Type = GraphQLWebSocketMessageType.GQL_CONNECTION_TERMINATE }).ConfigureAwait(false); + private async Task CloseAsync() + { + if (_clientWebSocket == null) + return; - Debug.WriteLine($"closing websocket {_clientWebSocket.GetHashCode()}"); - await _clientWebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None).ConfigureAwait(false); - _stateSubject.OnNext(GraphQLWebsocketConnectionState.Disconnected); + // don't attempt to close the websocket if it is in a failed state + if (_clientWebSocket.State != WebSocketState.Open && + _clientWebSocket.State != WebSocketState.CloseReceived && + _clientWebSocket.State != WebSocketState.CloseSent) + { + Debug.WriteLine($"websocket {_clientWebSocket.GetHashCode()} state = {_clientWebSocket.State}"); + return; } - #region IDisposable + Debug.WriteLine($"send \"connection_terminate\" message"); + await SendWebSocketMessageAsync(new GraphQLWebSocketRequest { Type = GraphQLWebSocketMessageType.GQL_CONNECTION_TERMINATE }).ConfigureAwait(false); + + Debug.WriteLine($"closing websocket {_clientWebSocket.GetHashCode()}"); + await _clientWebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None).ConfigureAwait(false); + _stateSubject.OnNext(GraphQLWebsocketConnectionState.Disconnected); + } + + #region IDisposable - public void Dispose() => Complete(); + public void Dispose() => Complete(); - /// - /// Cancels the current operation, closes the websocket connection and disposes of internal resources. - /// - public void Complete() + /// + /// Cancels the current operation, closes the websocket connection and disposes of internal resources. + /// + public void Complete() + { + lock (_completedLocker) { - lock (_completedLocker) - { - if (Completion == null) - Completion = CompleteAsync(); - } + if (Completion == null) + Completion = CompleteAsync(); } + } - /// - /// Task to await the completion (a.k.a. disposal) of this websocket. - /// - /// Async disposal as recommended by Stephen Cleary (https://blog.stephencleary.com/2013/03/async-oop-6-disposal.html) - public Task? Completion { get; private set; } - - private readonly object _completedLocker = new object(); - private async Task CompleteAsync() - { - Debug.WriteLine("disposing GraphQLHttpWebSocket..."); + /// + /// Task to await the completion (a.k.a. disposal) of this websocket. + /// + /// Async disposal as recommended by Stephen Cleary (https://blog.stephencleary.com/2013/03/async-oop-6-disposal.html) + public Task? Completion { get; private set; } - _incomingMessagesConnection?.Dispose(); + private readonly object _completedLocker = new object(); + private async Task CompleteAsync() + { + Debug.WriteLine("disposing GraphQLHttpWebSocket..."); - if (!_internalCancellationTokenSource.IsCancellationRequested) - _internalCancellationTokenSource.Cancel(); + _incomingMessagesConnection?.Dispose(); - await CloseAsync().ConfigureAwait(false); - _requestSubscription?.Dispose(); - _clientWebSocket?.Dispose(); + if (!_internalCancellationTokenSource.IsCancellationRequested) + _internalCancellationTokenSource.Cancel(); - _stateSubject?.OnCompleted(); - _stateSubject?.Dispose(); + await CloseAsync().ConfigureAwait(false); + _requestSubscription?.Dispose(); + _clientWebSocket?.Dispose(); - _exceptionSubject?.OnCompleted(); - _exceptionSubject?.Dispose(); - _internalCancellationTokenSource.Dispose(); + _stateSubject?.OnCompleted(); + _stateSubject?.Dispose(); - Debug.WriteLine("GraphQLHttpWebSocket disposed"); - } + _exceptionSubject?.OnCompleted(); + _exceptionSubject?.Dispose(); + _internalCancellationTokenSource.Dispose(); - #endregion + Debug.WriteLine("GraphQLHttpWebSocket disposed"); } + + #endregion } diff --git a/src/GraphQL.Client/Websocket/GraphQLWebsocketConnectionException.cs b/src/GraphQL.Client/Websocket/GraphQLWebsocketConnectionException.cs index a1416948..ea4140fb 100644 --- a/src/GraphQL.Client/Websocket/GraphQLWebsocketConnectionException.cs +++ b/src/GraphQL.Client/Websocket/GraphQLWebsocketConnectionException.cs @@ -1,24 +1,23 @@ using System.Runtime.Serialization; -namespace GraphQL.Client.Http.Websocket +namespace GraphQL.Client.Http.Websocket; + +[Serializable] +public class GraphQLWebsocketConnectionException: Exception { - [Serializable] - public class GraphQLWebsocketConnectionException: Exception + public GraphQLWebsocketConnectionException() { - public GraphQLWebsocketConnectionException() - { - } + } - protected GraphQLWebsocketConnectionException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } + protected GraphQLWebsocketConnectionException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } - public GraphQLWebsocketConnectionException(string message) : base(message) - { - } + public GraphQLWebsocketConnectionException(string message) : base(message) + { + } - public GraphQLWebsocketConnectionException(string message, Exception innerException) : base(message, innerException) - { - } + public GraphQLWebsocketConnectionException(string message, Exception innerException) : base(message, innerException) + { } } diff --git a/src/GraphQL.Primitives/ErrorPath.cs b/src/GraphQL.Primitives/ErrorPath.cs index dd449d73..a43bf02e 100644 --- a/src/GraphQL.Primitives/ErrorPath.cs +++ b/src/GraphQL.Primitives/ErrorPath.cs @@ -1,13 +1,12 @@ -namespace GraphQL +namespace GraphQL; + +public class ErrorPath : List { - public class ErrorPath : List + public ErrorPath() { - public ErrorPath() - { - } + } - public ErrorPath(IEnumerable collection) : base(collection) - { - } + public ErrorPath(IEnumerable collection) : base(collection) + { } } diff --git a/src/GraphQL.Primitives/GraphQLError.cs b/src/GraphQL.Primitives/GraphQLError.cs index 7b3e9a1a..29a94250 100644 --- a/src/GraphQL.Primitives/GraphQLError.cs +++ b/src/GraphQL.Primitives/GraphQLError.cs @@ -1,115 +1,114 @@ using System.Runtime.Serialization; -namespace GraphQL +namespace GraphQL; + +/// +/// Represents a GraphQL Error of a GraphQL Query +/// +public class GraphQLError : IEquatable { /// - /// Represents a GraphQL Error of a GraphQL Query + /// The locations of the error /// - public class GraphQLError : IEquatable - { - /// - /// The locations of the error - /// - [DataMember(Name = "locations")] - public GraphQLLocation[]? Locations { get; set; } + [DataMember(Name = "locations")] + public GraphQLLocation[]? Locations { get; set; } - /// - /// The message of the error - /// - [DataMember(Name = "message")] - public string Message { get; set; } + /// + /// The message of the error + /// + [DataMember(Name = "message")] + public string Message { get; set; } - /// - /// The Path of the error - /// - [DataMember(Name = "path")] - public ErrorPath? Path { get; set; } + /// + /// The Path of the error + /// + [DataMember(Name = "path")] + public ErrorPath? Path { get; set; } - /// - /// The extensions of the error - /// - [DataMember(Name = "extensions")] - public Map? Extensions { get; set; } + /// + /// The extensions of the error + /// + [DataMember(Name = "extensions")] + public Map? Extensions { get; set; } - /// - /// Returns a value that indicates whether this instance is equal to a specified object - /// - /// The object to compare with this instance - /// true if obj is an instance of and equals the value of the instance; otherwise, false - public override bool Equals(object? obj) => Equals(obj as GraphQLError); + /// + /// Returns a value that indicates whether this instance is equal to a specified object + /// + /// The object to compare with this instance + /// true if obj is an instance of and equals the value of the instance; otherwise, false + public override bool Equals(object? obj) => Equals(obj as GraphQLError); - /// - /// Returns a value that indicates whether this instance is equal to a specified object - /// - /// The object to compare with this instance - /// true if obj is an instance of and equals the value of the instance; otherwise, false - public bool Equals(GraphQLError? other) + /// + /// Returns a value that indicates whether this instance is equal to a specified object + /// + /// The object to compare with this instance + /// true if obj is an instance of and equals the value of the instance; otherwise, false + public bool Equals(GraphQLError? other) + { + if (other == null) + { return false; } + if (ReferenceEquals(this, other)) + { return true; } { - if (other == null) - { return false; } - if (ReferenceEquals(this, other)) - { return true; } + if (Locations != null && other.Locations != null) { - if (Locations != null && other.Locations != null) - { - if (!Locations.SequenceEqual(other.Locations)) - { return false; } - } - else if (Locations != null && other.Locations == null) - { return false; } - else if (Locations == null && other.Locations != null) + if (!Locations.SequenceEqual(other.Locations)) { return false; } } - if (!EqualityComparer.Default.Equals(Message, other.Message)) + else if (Locations != null && other.Locations == null) { return false; } + else if (Locations == null && other.Locations != null) + { return false; } + } + if (!EqualityComparer.Default.Equals(Message, other.Message)) + { return false; } + { + if (Path != null && other.Path != null) { - if (Path != null && other.Path != null) - { - if (!Path.SequenceEqual(other.Path)) - { return false; } - } - else if (Path != null && other.Path == null) - { return false; } - else if (Path == null && other.Path != null) + if (!Path.SequenceEqual(other.Path)) { return false; } } - return true; + else if (Path != null && other.Path == null) + { return false; } + else if (Path == null && other.Path != null) + { return false; } } + return true; + } - /// - /// - /// - public override int GetHashCode() + /// + /// + /// + public override int GetHashCode() + { + var hashCode = 0; + if (Locations != null) { - var hashCode = 0; - if (Locations != null) - { - hashCode ^= EqualityComparer.Default.GetHashCode(Locations); - } - hashCode ^= EqualityComparer.Default.GetHashCode(Message); - if (Path != null) - { - hashCode ^= EqualityComparer.Default.GetHashCode(Path); - } - return hashCode; + hashCode ^= EqualityComparer.Default.GetHashCode(Locations); + } + hashCode ^= EqualityComparer.Default.GetHashCode(Message); + if (Path != null) + { + hashCode ^= EqualityComparer.Default.GetHashCode(Path); } + return hashCode; + } - /// - /// Tests whether two specified instances are equivalent - /// - /// The instance that is to the left of the equality operator - /// The instance that is to the right of the equality operator - /// true if left and right are equal; otherwise, false - public static bool operator ==(GraphQLError? left, GraphQLError? right) => - EqualityComparer.Default.Equals(left, right); + /// + /// Tests whether two specified instances are equivalent + /// + /// The instance that is to the left of the equality operator + /// The instance that is to the right of the equality operator + /// true if left and right are equal; otherwise, false + public static bool operator ==(GraphQLError? left, GraphQLError? right) => + EqualityComparer.Default.Equals(left, right); - /// - /// Tests whether two specified instances are not equal - /// - /// The instance that is to the left of the not equal operator - /// The instance that is to the right of the not equal operator - /// true if left and right are unequal; otherwise, false - public static bool operator !=(GraphQLError? left, GraphQLError? right) => - !EqualityComparer.Default.Equals(left, right); - } + /// + /// Tests whether two specified instances are not equal + /// + /// The instance that is to the left of the not equal operator + /// The instance that is to the right of the not equal operator + /// true if left and right are unequal; otherwise, false + public static bool operator !=(GraphQLError? left, GraphQLError? right) => + !EqualityComparer.Default.Equals(left, right); } diff --git a/src/GraphQL.Primitives/GraphQLLocation.cs b/src/GraphQL.Primitives/GraphQLLocation.cs index 4a499031..efec6256 100644 --- a/src/GraphQL.Primitives/GraphQLLocation.cs +++ b/src/GraphQL.Primitives/GraphQLLocation.cs @@ -1,64 +1,63 @@ -namespace GraphQL +namespace GraphQL; + +/// +/// Represents a GraphQL Location of a GraphQL Query +/// +public sealed class GraphQLLocation : IEquatable { /// - /// Represents a GraphQL Location of a GraphQL Query + /// The Column /// - public sealed class GraphQLLocation : IEquatable - { - /// - /// The Column - /// - public uint Column { get; set; } + public uint Column { get; set; } - /// - /// The Line - /// - public uint Line { get; set; } + /// + /// The Line + /// + public uint Line { get; set; } - /// - /// Returns a value that indicates whether this instance is equal to a specified object - /// - /// The object to compare with this instance - /// true if obj is an instance of and equals the value of the instance; otherwise, false - public override bool Equals(object obj) => Equals(obj as GraphQLLocation); + /// + /// Returns a value that indicates whether this instance is equal to a specified object + /// + /// The object to compare with this instance + /// true if obj is an instance of and equals the value of the instance; otherwise, false + public override bool Equals(object obj) => Equals(obj as GraphQLLocation); - /// - /// Returns a value that indicates whether this instance is equal to a specified object - /// - /// The object to compare with this instance - /// true if obj is an instance of and equals the value of the instance; otherwise, false - public bool Equals(GraphQLLocation? other) - { - if (other == null) - { return false; } - if (ReferenceEquals(this, other)) - { return true; } - return EqualityComparer.Default.Equals(Column, other.Column) && - EqualityComparer.Default.Equals(Line, other.Line); - } + /// + /// Returns a value that indicates whether this instance is equal to a specified object + /// + /// The object to compare with this instance + /// true if obj is an instance of and equals the value of the instance; otherwise, false + public bool Equals(GraphQLLocation? other) + { + if (other == null) + { return false; } + if (ReferenceEquals(this, other)) + { return true; } + return EqualityComparer.Default.Equals(Column, other.Column) && + EqualityComparer.Default.Equals(Line, other.Line); + } - /// - /// - /// - public override int GetHashCode() => - Column.GetHashCode() ^ Line.GetHashCode(); + /// + /// + /// + public override int GetHashCode() => + Column.GetHashCode() ^ Line.GetHashCode(); - /// - /// Tests whether two specified instances are equivalent - /// - /// The instance that is to the left of the equality operator - /// The instance that is to the right of the equality operator - /// true if left and right are equal; otherwise, false - public static bool operator ==(GraphQLLocation? left, GraphQLLocation? right) => - EqualityComparer.Default.Equals(left, right); + /// + /// Tests whether two specified instances are equivalent + /// + /// The instance that is to the left of the equality operator + /// The instance that is to the right of the equality operator + /// true if left and right are equal; otherwise, false + public static bool operator ==(GraphQLLocation? left, GraphQLLocation? right) => + EqualityComparer.Default.Equals(left, right); - /// - /// Tests whether two specified instances are not equal - /// - /// The instance that is to the left of the not equal operator - /// The instance that is to the right of the not equal operator - /// true if left and right are unequal; otherwise, false - public static bool operator !=(GraphQLLocation? left, GraphQLLocation? right) => - !EqualityComparer.Default.Equals(left, right); - } + /// + /// Tests whether two specified instances are not equal + /// + /// The instance that is to the left of the not equal operator + /// The instance that is to the right of the not equal operator + /// true if left and right are unequal; otherwise, false + public static bool operator !=(GraphQLLocation? left, GraphQLLocation? right) => + !EqualityComparer.Default.Equals(left, right); } diff --git a/src/GraphQL.Primitives/GraphQLRequest.cs b/src/GraphQL.Primitives/GraphQLRequest.cs index 5dbb766d..7d9ee10c 100644 --- a/src/GraphQL.Primitives/GraphQLRequest.cs +++ b/src/GraphQL.Primitives/GraphQLRequest.cs @@ -1,112 +1,111 @@ -namespace GraphQL +namespace GraphQL; + +/// +/// A GraphQL request +/// +public class GraphQLRequest : Dictionary, IEquatable { + public const string OPERATION_NAME_KEY = "operationName"; + public const string QUERY_KEY = "query"; + public const string VARIABLES_KEY = "variables"; + public const string EXTENSIONS_KEY = "extensions"; + /// - /// A GraphQL request + /// The Query /// - public class GraphQLRequest : Dictionary, IEquatable + public string Query { - public const string OPERATION_NAME_KEY = "operationName"; - public const string QUERY_KEY = "query"; - public const string VARIABLES_KEY = "variables"; - public const string EXTENSIONS_KEY = "extensions"; - - /// - /// The Query - /// - public string Query - { - get => TryGetValue(QUERY_KEY, out object value) ? (string)value : null; - set => this[QUERY_KEY] = value; - } + get => TryGetValue(QUERY_KEY, out object value) ? (string)value : null; + set => this[QUERY_KEY] = value; + } - /// - /// The name of the Operation - /// - public string? OperationName - { - get => TryGetValue(OPERATION_NAME_KEY, out object value) ? (string)value : null; - set => this[OPERATION_NAME_KEY] = value; - } + /// + /// The name of the Operation + /// + public string? OperationName + { + get => TryGetValue(OPERATION_NAME_KEY, out object value) ? (string)value : null; + set => this[OPERATION_NAME_KEY] = value; + } - /// - /// Represents the request variables - /// - public object? Variables - { - get => TryGetValue(VARIABLES_KEY, out object value) ? value : null; - set => this[VARIABLES_KEY] = value; - } + /// + /// Represents the request variables + /// + public object? Variables + { + get => TryGetValue(VARIABLES_KEY, out object value) ? value : null; + set => this[VARIABLES_KEY] = value; + } - /// - /// Represents the request extensions - /// - public object? Extensions - { - get => TryGetValue(EXTENSIONS_KEY, out object value) ? value : null; - set => this[EXTENSIONS_KEY] = value; - } + /// + /// Represents the request extensions + /// + public object? Extensions + { + get => TryGetValue(EXTENSIONS_KEY, out object value) ? value : null; + set => this[EXTENSIONS_KEY] = value; + } - public GraphQLRequest() { } + public GraphQLRequest() { } - public GraphQLRequest(string query, object? variables = null, string? operationName = null, object? extensions = null) - { - Query = query; - Variables = variables; - OperationName = operationName; - Extensions = extensions; - } + public GraphQLRequest(string query, object? variables = null, string? operationName = null, object? extensions = null) + { + Query = query; + Variables = variables; + OperationName = operationName; + Extensions = extensions; + } - public GraphQLRequest(GraphQLRequest other): base(other) { } + public GraphQLRequest(GraphQLRequest other): base(other) { } - /// - /// Returns a value that indicates whether this instance is equal to a specified object - /// - /// The object to compare with this instance - /// true if obj is an instance of and equals the value of the instance; otherwise, false - public override bool Equals(object? obj) - { - if (obj is null) - return false; - if (ReferenceEquals(this, obj)) - return true; - if (obj.GetType() != GetType()) - return false; - return Equals((GraphQLRequest)obj); - } + /// + /// Returns a value that indicates whether this instance is equal to a specified object + /// + /// The object to compare with this instance + /// true if obj is an instance of and equals the value of the instance; otherwise, false + public override bool Equals(object? obj) + { + if (obj is null) + return false; + if (ReferenceEquals(this, obj)) + return true; + if (obj.GetType() != GetType()) + return false; + return Equals((GraphQLRequest)obj); + } - /// - /// Returns a value that indicates whether this instance is equal to a specified object - /// - /// The object to compare with this instance - /// true if obj is an instance of and equals the value of the instance; otherwise, false - public virtual bool Equals(GraphQLRequest? other) - { - if (other is null) - return false; - if (ReferenceEquals(this, other)) - return true; - return Count == other.Count && !this.Except(other).Any(); - } + /// + /// Returns a value that indicates whether this instance is equal to a specified object + /// + /// The object to compare with this instance + /// true if obj is an instance of and equals the value of the instance; otherwise, false + public virtual bool Equals(GraphQLRequest? other) + { + if (other is null) + return false; + if (ReferenceEquals(this, other)) + return true; + return Count == other.Count && !this.Except(other).Any(); + } - /// - /// - /// - public override int GetHashCode() => (Query, OperationName, Variables, Extensions).GetHashCode(); + /// + /// + /// + public override int GetHashCode() => (Query, OperationName, Variables, Extensions).GetHashCode(); - /// - /// Tests whether two specified instances are equivalent - /// - /// The instance that is to the left of the equality operator - /// The instance that is to the right of the equality operator - /// true if left and right are equal; otherwise, false - public static bool operator ==(GraphQLRequest? left, GraphQLRequest? right) => EqualityComparer.Default.Equals(left, right); + /// + /// Tests whether two specified instances are equivalent + /// + /// The instance that is to the left of the equality operator + /// The instance that is to the right of the equality operator + /// true if left and right are equal; otherwise, false + public static bool operator ==(GraphQLRequest? left, GraphQLRequest? right) => EqualityComparer.Default.Equals(left, right); - /// - /// Tests whether two specified instances are not equal - /// - /// The instance that is to the left of the not equal operator - /// The instance that is to the right of the not equal operator - /// true if left and right are unequal; otherwise, false - public static bool operator !=(GraphQLRequest? left, GraphQLRequest? right) => !(left == right); - } + /// + /// Tests whether two specified instances are not equal + /// + /// The instance that is to the left of the not equal operator + /// The instance that is to the right of the not equal operator + /// true if left and right are unequal; otherwise, false + public static bool operator !=(GraphQLRequest? left, GraphQLRequest? right) => !(left == right); } diff --git a/src/GraphQL.Primitives/GraphQLResponse.cs b/src/GraphQL.Primitives/GraphQLResponse.cs index c4c8a010..adcd6457 100644 --- a/src/GraphQL.Primitives/GraphQLResponse.cs +++ b/src/GraphQL.Primitives/GraphQLResponse.cs @@ -1,89 +1,88 @@ using System.Runtime.Serialization; -namespace GraphQL +namespace GraphQL; + +public class GraphQLResponse : IGraphQLResponse, IEquatable?> { - public class GraphQLResponse : IGraphQLResponse, IEquatable?> - { - [DataMember(Name = "data")] - public T Data { get; set; } - object IGraphQLResponse.Data => Data; + [DataMember(Name = "data")] + public T Data { get; set; } + object IGraphQLResponse.Data => Data; - [DataMember(Name = "errors")] - public GraphQLError[]? Errors { get; set; } + [DataMember(Name = "errors")] + public GraphQLError[]? Errors { get; set; } - [DataMember(Name = "extensions")] - public Map? Extensions { get; set; } + [DataMember(Name = "extensions")] + public Map? Extensions { get; set; } - public override bool Equals(object? obj) => Equals(obj as GraphQLResponse); + public override bool Equals(object? obj) => Equals(obj as GraphQLResponse); - public bool Equals(GraphQLResponse? other) - { - if (other == null) - { return false; } - if (ReferenceEquals(this, other)) - { return true; } - if (!EqualityComparer.Default.Equals(Data, other.Data)) - { return false; } + public bool Equals(GraphQLResponse? other) + { + if (other == null) + { return false; } + if (ReferenceEquals(this, other)) + { return true; } + if (!EqualityComparer.Default.Equals(Data, other.Data)) + { return false; } - if (Errors != null && other.Errors != null) - { - if (!Enumerable.SequenceEqual(Errors, other.Errors)) - { return false; } - } - else if (Errors != null && other.Errors == null) - { return false; } - else if (Errors == null && other.Errors != null) + if (Errors != null && other.Errors != null) + { + if (!Enumerable.SequenceEqual(Errors, other.Errors)) { return false; } + } + else if (Errors != null && other.Errors == null) + { return false; } + else if (Errors == null && other.Errors != null) + { return false; } - if (Extensions != null && other.Extensions != null) - { - if (!Enumerable.SequenceEqual(Extensions, other.Extensions)) - { return false; } - } - else if (Extensions != null && other.Extensions == null) - { return false; } - else if (Extensions == null && other.Extensions != null) + if (Extensions != null && other.Extensions != null) + { + if (!Enumerable.SequenceEqual(Extensions, other.Extensions)) { return false; } - - return true; } + else if (Extensions != null && other.Extensions == null) + { return false; } + else if (Extensions == null && other.Extensions != null) + { return false; } - public override int GetHashCode() + return true; + } + + public override int GetHashCode() + { + unchecked { - unchecked + var hashCode = EqualityComparer.Default.GetHashCode(Data); { - var hashCode = EqualityComparer.Default.GetHashCode(Data); + if (Errors != null) { - if (Errors != null) - { - foreach (var element in Errors) - { - hashCode = (hashCode * 397) ^ EqualityComparer.Default.GetHashCode(element); - } - } - else + foreach (var element in Errors) { - hashCode = (hashCode * 397) ^ 0; + hashCode = (hashCode * 397) ^ EqualityComparer.Default.GetHashCode(element); } + } + else + { + hashCode = (hashCode * 397) ^ 0; + } - if (Extensions != null) - { - foreach (var element in Extensions) - { - hashCode = (hashCode * 397) ^ EqualityComparer>.Default.GetHashCode(element); - } - } - else + if (Extensions != null) + { + foreach (var element in Extensions) { - hashCode = (hashCode * 397) ^ 0; + hashCode = (hashCode * 397) ^ EqualityComparer>.Default.GetHashCode(element); } } - return hashCode; + else + { + hashCode = (hashCode * 397) ^ 0; + } } + return hashCode; } + } - public static bool operator ==(GraphQLResponse? response1, GraphQLResponse? response2) => EqualityComparer?>.Default.Equals(response1, response2); + public static bool operator ==(GraphQLResponse? response1, GraphQLResponse? response2) => EqualityComparer?>.Default.Equals(response1, response2); - public static bool operator !=(GraphQLResponse? response1, GraphQLResponse? response2) => !(response1 == response2); - } + public static bool operator !=(GraphQLResponse? response1, GraphQLResponse? response2) => !(response1 == response2); } diff --git a/src/GraphQL.Primitives/IGraphQLResponse.cs b/src/GraphQL.Primitives/IGraphQLResponse.cs index 789d686f..4d4c2f93 100644 --- a/src/GraphQL.Primitives/IGraphQLResponse.cs +++ b/src/GraphQL.Primitives/IGraphQLResponse.cs @@ -1,11 +1,10 @@ -namespace GraphQL +namespace GraphQL; + +public interface IGraphQLResponse { - public interface IGraphQLResponse - { - object Data { get; } + object Data { get; } - GraphQLError[]? Errors { get; set; } + GraphQLError[]? Errors { get; set; } - Map? Extensions { get; set; } - } + Map? Extensions { get; set; } } diff --git a/src/GraphQL.Primitives/Map.cs b/src/GraphQL.Primitives/Map.cs index b51f5a85..7c5823f1 100644 --- a/src/GraphQL.Primitives/Map.cs +++ b/src/GraphQL.Primitives/Map.cs @@ -1,7 +1,6 @@ -namespace GraphQL -{ - /// - /// A type equivalent to a javascript map. Create a custom json converter for this class to customize your serializers behaviour - /// - public class Map : Dictionary { } -} +namespace GraphQL; + +/// +/// A type equivalent to a javascript map. Create a custom json converter for this class to customize your serializers behaviour +/// +public class Map : Dictionary { } diff --git a/tests/GraphQL.Client.Serializer.Tests/BaseSerializeNoCamelCaseTest.cs b/tests/GraphQL.Client.Serializer.Tests/BaseSerializeNoCamelCaseTest.cs index ce3d51f6..9d97fea3 100644 --- a/tests/GraphQL.Client.Serializer.Tests/BaseSerializeNoCamelCaseTest.cs +++ b/tests/GraphQL.Client.Serializer.Tests/BaseSerializeNoCamelCaseTest.cs @@ -9,65 +9,64 @@ using GraphQL.Client.Tests.Common.Helpers; using Xunit; -namespace GraphQL.Client.Serializer.Tests +namespace GraphQL.Client.Serializer.Tests; + +public abstract class BaseSerializeNoCamelCaseTest { - public abstract class BaseSerializeNoCamelCaseTest - { - public IGraphQLWebsocketJsonSerializer ClientSerializer { get; } + public IGraphQLWebsocketJsonSerializer ClientSerializer { get; } - public IGraphQLTextSerializer ServerSerializer { get; } + public IGraphQLTextSerializer ServerSerializer { get; } - public IGraphQLClient ChatClient { get; } + public IGraphQLClient ChatClient { get; } - public IGraphQLClient StarWarsClient { get; } + public IGraphQLClient StarWarsClient { get; } - protected BaseSerializeNoCamelCaseTest(IGraphQLWebsocketJsonSerializer clientSerializer, IGraphQLTextSerializer serverSerializer) - { - ClientSerializer = clientSerializer; - ServerSerializer = serverSerializer; - ChatClient = GraphQLLocalExecutionClient.New(Common.GetChatSchema(), clientSerializer, serverSerializer); - StarWarsClient = GraphQLLocalExecutionClient.New(Common.GetStarWarsSchema(), clientSerializer, serverSerializer); - } + protected BaseSerializeNoCamelCaseTest(IGraphQLWebsocketJsonSerializer clientSerializer, IGraphQLTextSerializer serverSerializer) + { + ClientSerializer = clientSerializer; + ServerSerializer = serverSerializer; + ChatClient = GraphQLLocalExecutionClient.New(Common.GetChatSchema(), clientSerializer, serverSerializer); + StarWarsClient = GraphQLLocalExecutionClient.New(Common.GetStarWarsSchema(), clientSerializer, serverSerializer); + } - [Theory] - [ClassData(typeof(SerializeToStringTestData))] - public void SerializeToStringTest(string expectedJson, GraphQLRequest request) - { - var json = ClientSerializer.SerializeToString(request).RemoveWhitespace(); - json.Should().Be(expectedJson.RemoveWhitespace()); - } + [Theory] + [ClassData(typeof(SerializeToStringTestData))] + public void SerializeToStringTest(string expectedJson, GraphQLRequest request) + { + var json = ClientSerializer.SerializeToString(request).RemoveWhitespace(); + json.Should().Be(expectedJson.RemoveWhitespace()); + } - [Theory] - [ClassData(typeof(SerializeToBytesTestData))] - public void SerializeToBytesTest(string expectedJson, GraphQLWebSocketRequest request) - { - var json = Encoding.UTF8.GetString(ClientSerializer.SerializeToBytes(request)).RemoveWhitespace(); - json.Should().Be(expectedJson.RemoveWhitespace()); - } + [Theory] + [ClassData(typeof(SerializeToBytesTestData))] + public void SerializeToBytesTest(string expectedJson, GraphQLWebSocketRequest request) + { + var json = Encoding.UTF8.GetString(ClientSerializer.SerializeToBytes(request)).RemoveWhitespace(); + json.Should().Be(expectedJson.RemoveWhitespace()); + } - [Fact] - public async void WorksWithoutCamelCaseNamingStrategy() - { + [Fact] + public async void WorksWithoutCamelCaseNamingStrategy() + { - const string message = "some random testing message"; - var graphQLRequest = new GraphQLRequest( - @"mutation($input: MessageInputType){ + const string message = "some random testing message"; + var graphQLRequest = new GraphQLRequest( + @"mutation($input: MessageInputType){ addMessage(message: $input){ content } }", - new + new + { + input = new { - input = new - { - fromId = "2", - content = message, - sentAt = DateTime.Now - } - }); - var response = await ChatClient.SendMutationAsync(graphQLRequest, () => new { addMessage = new { content = "" } }); + fromId = "2", + content = message, + sentAt = DateTime.Now + } + }); + var response = await ChatClient.SendMutationAsync(graphQLRequest, () => new { addMessage = new { content = "" } }); - Assert.Equal(message, response.Data.addMessage.content); - } + Assert.Equal(message, response.Data.addMessage.content); } } diff --git a/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs b/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs index 8efdccdf..2021a792 100644 --- a/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs +++ b/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs @@ -18,115 +18,115 @@ using GraphQL.Client.Tests.Common.StarWars.TestData; using Xunit; -namespace GraphQL.Client.Serializer.Tests +namespace GraphQL.Client.Serializer.Tests; + +public abstract class BaseSerializerTest { - public abstract class BaseSerializerTest - { - public IGraphQLWebsocketJsonSerializer ClientSerializer { get; } + public IGraphQLWebsocketJsonSerializer ClientSerializer { get; } - public IGraphQLTextSerializer ServerSerializer { get; } + public IGraphQLTextSerializer ServerSerializer { get; } - public IGraphQLClient ChatClient { get; } + public IGraphQLClient ChatClient { get; } - public IGraphQLClient StarWarsClient { get; } + public IGraphQLClient StarWarsClient { get; } - protected BaseSerializerTest(IGraphQLWebsocketJsonSerializer clientSerializer, IGraphQLTextSerializer serverSerializer) - { - ClientSerializer = clientSerializer; - ServerSerializer = serverSerializer; - ChatClient = GraphQLLocalExecutionClient.New(Common.GetChatSchema(), clientSerializer, serverSerializer); - StarWarsClient = GraphQLLocalExecutionClient.New(Common.GetStarWarsSchema(), clientSerializer, serverSerializer); - } + protected BaseSerializerTest(IGraphQLWebsocketJsonSerializer clientSerializer, IGraphQLTextSerializer serverSerializer) + { + ClientSerializer = clientSerializer; + ServerSerializer = serverSerializer; + ChatClient = GraphQLLocalExecutionClient.New(Common.GetChatSchema(), clientSerializer, serverSerializer); + StarWarsClient = GraphQLLocalExecutionClient.New(Common.GetStarWarsSchema(), clientSerializer, serverSerializer); + } - [Theory] - [ClassData(typeof(SerializeToStringTestData))] - public void SerializeToStringTest(string expectedJson, GraphQLRequest request) - { - var json = ClientSerializer.SerializeToString(request).RemoveWhitespace(); - json.Should().BeEquivalentTo(expectedJson.RemoveWhitespace()); - } + [Theory] + [ClassData(typeof(SerializeToStringTestData))] + public void SerializeToStringTest(string expectedJson, GraphQLRequest request) + { + var json = ClientSerializer.SerializeToString(request).RemoveWhitespace(); + json.Should().BeEquivalentTo(expectedJson.RemoveWhitespace()); + } - [Theory] - [ClassData(typeof(SerializeToBytesTestData))] - public void SerializeToBytesTest(string expectedJson, GraphQLWebSocketRequest request) - { - var json = Encoding.UTF8.GetString(ClientSerializer.SerializeToBytes(request)).RemoveWhitespace(); - json.Should().BeEquivalentTo(expectedJson.RemoveWhitespace()); - } + [Theory] + [ClassData(typeof(SerializeToBytesTestData))] + public void SerializeToBytesTest(string expectedJson, GraphQLWebSocketRequest request) + { + var json = Encoding.UTF8.GetString(ClientSerializer.SerializeToBytes(request)).RemoveWhitespace(); + json.Should().BeEquivalentTo(expectedJson.RemoveWhitespace()); + } - [Theory] - [ClassData(typeof(DeserializeResponseTestData))] - public async void DeserializeFromUtf8StreamTest(string json, IGraphQLResponse expectedResponse) - { - var jsonBytes = Encoding.UTF8.GetBytes(json); - await using var ms = new MemoryStream(jsonBytes); - var response = await DeserializeToUnknownType(expectedResponse.Data?.GetType() ?? typeof(object), ms); + [Theory] + [ClassData(typeof(DeserializeResponseTestData))] + public async void DeserializeFromUtf8StreamTest(string json, IGraphQLResponse expectedResponse) + { + var jsonBytes = Encoding.UTF8.GetBytes(json); + await using var ms = new MemoryStream(jsonBytes); + var response = await DeserializeToUnknownType(expectedResponse.Data?.GetType() ?? typeof(object), ms); - //var response = await Serializer.DeserializeFromUtf8StreamAsync(ms, CancellationToken.None); + //var response = await Serializer.DeserializeFromUtf8StreamAsync(ms, CancellationToken.None); - response.Data.Should().BeEquivalentTo(expectedResponse.Data, options => options.WithAutoConversion()); + response.Data.Should().BeEquivalentTo(expectedResponse.Data, options => options.WithAutoConversion()); - if (expectedResponse.Errors is null) - response.Errors.Should().BeNull(); - else + if (expectedResponse.Errors is null) + response.Errors.Should().BeNull(); + else + { + using (new AssertionScope()) { - using (new AssertionScope()) + response.Errors.Should().NotBeNull(); + response.Errors.Should().HaveSameCount(expectedResponse.Errors); + for (int i = 0; i < expectedResponse.Errors.Length; i++) { - response.Errors.Should().NotBeNull(); - response.Errors.Should().HaveSameCount(expectedResponse.Errors); - for (int i = 0; i < expectedResponse.Errors.Length; i++) - { - response.Errors[i].Message.Should().BeEquivalentTo(expectedResponse.Errors[i].Message); - response.Errors[i].Locations.Should().BeEquivalentTo(expectedResponse.Errors[i].Locations?.ToList()); - response.Errors[i].Path.Should().BeEquivalentTo(expectedResponse.Errors[i].Path); - response.Errors[i].Extensions.Should().BeEquivalentTo(expectedResponse.Errors[i].Extensions); - } + response.Errors[i].Message.Should().BeEquivalentTo(expectedResponse.Errors[i].Message); + response.Errors[i].Locations.Should().BeEquivalentTo(expectedResponse.Errors[i].Locations?.ToList()); + response.Errors[i].Path.Should().BeEquivalentTo(expectedResponse.Errors[i].Path); + response.Errors[i].Extensions.Should().BeEquivalentTo(expectedResponse.Errors[i].Extensions); } } + } - if (expectedResponse.Extensions == null) - response.Extensions.Should().BeNull(); - else + if (expectedResponse.Extensions == null) + response.Extensions.Should().BeNull(); + else + { + foreach (var element in expectedResponse.Extensions) { - foreach (var element in expectedResponse.Extensions) - { - response.Extensions.Should().ContainKey(element.Key); - response.Extensions[element.Key].Should().BeEquivalentTo(element.Value); - } + response.Extensions.Should().ContainKey(element.Key); + response.Extensions[element.Key].Should().BeEquivalentTo(element.Value); } } + } - public async Task DeserializeToUnknownType(Type dataType, Stream stream) - { - MethodInfo mi = ClientSerializer.GetType().GetMethod("DeserializeFromUtf8StreamAsync", BindingFlags.Instance | BindingFlags.Public); - MethodInfo mi2 = mi.MakeGenericMethod(dataType); - var task = (Task)mi2.Invoke(ClientSerializer, new object[] { stream, CancellationToken.None }); - await task; - var resultProperty = task.GetType().GetProperty("Result", BindingFlags.Public | BindingFlags.Instance); - var result = resultProperty.GetValue(task); - return (IGraphQLResponse)result; - } + public async Task DeserializeToUnknownType(Type dataType, Stream stream) + { + MethodInfo mi = ClientSerializer.GetType().GetMethod("DeserializeFromUtf8StreamAsync", BindingFlags.Instance | BindingFlags.Public); + MethodInfo mi2 = mi.MakeGenericMethod(dataType); + var task = (Task)mi2.Invoke(ClientSerializer, new object[] { stream, CancellationToken.None }); + await task; + var resultProperty = task.GetType().GetProperty("Result", BindingFlags.Public | BindingFlags.Instance); + var result = resultProperty.GetValue(task); + return (IGraphQLResponse)result; + } - [Fact] - public async void CanDeserializeExtensions() - { - var response = await ChatClient.SendQueryAsync( - new GraphQLRequest("query { extensionsTest }"), - () => new { extensionsTest = "" }); + [Fact] + public async void CanDeserializeExtensions() + { + var response = await ChatClient.SendQueryAsync( + new GraphQLRequest("query { extensionsTest }"), + () => new { extensionsTest = "" }); - response.Errors.Should().NotBeNull(); - response.Errors.Should().ContainSingle(); - response.Errors[0].Extensions.Should().NotBeNull(); - response.Errors[0].Extensions.Should().ContainKey("data"); + response.Errors.Should().NotBeNull(); + response.Errors.Should().ContainSingle(); + response.Errors[0].Extensions.Should().NotBeNull(); + response.Errors[0].Extensions.Should().ContainKey("data"); - response.Errors[0].Extensions["data"].Should().BeEquivalentTo(ChatQuery.TestExtensions); - } + response.Errors[0].Extensions["data"].Should().BeEquivalentTo(ChatQuery.TestExtensions); + } - [Theory] - [ClassData(typeof(StarWarsHumans))] - public async void CanDoSerializationWithAnonymousTypes(int id, string name) - { - var graphQLRequest = new GraphQLRequest(@" + [Theory] + [ClassData(typeof(StarWarsHumans))] + public async void CanDoSerializationWithAnonymousTypes(int id, string name) + { + var graphQLRequest = new GraphQLRequest(@" query Human($id: String!){ human(id: $id) { name @@ -138,62 +138,61 @@ query Droid($id: String!) { name } }", - new { id = id.ToString() }, - "Human"); + new { id = id.ToString() }, + "Human"); - var response = await StarWarsClient.SendQueryAsync(graphQLRequest, () => new { Human = new { Name = string.Empty } }); + var response = await StarWarsClient.SendQueryAsync(graphQLRequest, () => new { Human = new { Name = string.Empty } }); - Assert.Null(response.Errors); - Assert.Equal(name, response.Data.Human.Name); - } + Assert.Null(response.Errors); + Assert.Equal(name, response.Data.Human.Name); + } - [Fact] - public async void CanDoSerializationWithPredefinedTypes() - { - const string message = "some random testing message"; - var response = await ChatClient.AddMessageAsync(message); + [Fact] + public async void CanDoSerializationWithPredefinedTypes() + { + const string message = "some random testing message"; + var response = await ChatClient.AddMessageAsync(message); - Assert.Equal(message, response.Data.AddMessage.Content); - } + Assert.Equal(message, response.Data.AddMessage.Content); + } - public class WithNullable - { - public int? NullableInt { get; set; } - } + public class WithNullable + { + public int? NullableInt { get; set; } + } - [Fact] - public void CanSerializeNullableInt() + [Fact] + public void CanSerializeNullableInt() + { + Action action = () => ClientSerializer.SerializeToString(new GraphQLRequest { - Action action = () => ClientSerializer.SerializeToString(new GraphQLRequest + Query = "{}", + Variables = new WithNullable { - Query = "{}", - Variables = new WithNullable - { - NullableInt = 2 - } - }); + NullableInt = 2 + } + }); - action.Should().NotThrow(); - } + action.Should().NotThrow(); + } - public class WithNullableStruct - { - public DateTime? NullableStruct { get; set; } - } + public class WithNullableStruct + { + public DateTime? NullableStruct { get; set; } + } - [Fact] - public void CanSerializeNullableStruct() + [Fact] + public void CanSerializeNullableStruct() + { + Action action = () => ClientSerializer.SerializeToString(new GraphQLRequest { - Action action = () => ClientSerializer.SerializeToString(new GraphQLRequest + Query = "{}", + Variables = new WithNullableStruct { - Query = "{}", - Variables = new WithNullableStruct - { - NullableStruct = DateTime.Now - } - }); + NullableStruct = DateTime.Now + } + }); - action.Should().NotThrow(); - } + action.Should().NotThrow(); } } diff --git a/tests/GraphQL.Client.Serializer.Tests/ConsistencyTests.cs b/tests/GraphQL.Client.Serializer.Tests/ConsistencyTests.cs index 2fe14e7c..3bafce7c 100644 --- a/tests/GraphQL.Client.Serializer.Tests/ConsistencyTests.cs +++ b/tests/GraphQL.Client.Serializer.Tests/ConsistencyTests.cs @@ -6,14 +6,14 @@ using Newtonsoft.Json; using Xunit; -namespace GraphQL.Client.Serializer.Tests +namespace GraphQL.Client.Serializer.Tests; + +public class ConsistencyTests { - public class ConsistencyTests + [Fact] + public void MapConvertersShouldBehaveConsistent() { - [Fact] - public void MapConvertersShouldBehaveConsistent() - { - const string json = @"{ + const string json = @"{ ""array"": [ ""some stuff"", ""something else"" @@ -29,34 +29,33 @@ public void MapConvertersShouldBehaveConsistent() {""number"": 567.8} ] }"; - - var newtonsoftSerializer = new NewtonsoftJsonSerializer(); - var systemTextJsonSerializer = new SystemTextJsonSerializer(); + + var newtonsoftSerializer = new NewtonsoftJsonSerializer(); + var systemTextJsonSerializer = new SystemTextJsonSerializer(); - var newtonsoftMap = JsonConvert.DeserializeObject(json, newtonsoftSerializer.JsonSerializerSettings); - var systemTextJsonMap = System.Text.Json.JsonSerializer.Deserialize(json, systemTextJsonSerializer.Options); + var newtonsoftMap = JsonConvert.DeserializeObject(json, newtonsoftSerializer.JsonSerializerSettings); + var systemTextJsonMap = System.Text.Json.JsonSerializer.Deserialize(json, systemTextJsonSerializer.Options); - using(new AssertionScope()) - { - CompareMaps(newtonsoftMap, systemTextJsonMap); - } - - newtonsoftMap.Should().BeEquivalentTo(systemTextJsonMap, options => options - .RespectingRuntimeTypes()); + using(new AssertionScope()) + { + CompareMaps(newtonsoftMap, systemTextJsonMap); } - private void CompareMaps(Dictionary first, Dictionary second) + newtonsoftMap.Should().BeEquivalentTo(systemTextJsonMap, options => options + .RespectingRuntimeTypes()); + } + + private void CompareMaps(Dictionary first, Dictionary second) + { + foreach (var keyValuePair in first) { - foreach (var keyValuePair in first) - { - second.Should().ContainKey(keyValuePair.Key); - second[keyValuePair.Key].Should().BeOfType(keyValuePair.Value.GetType()); - if(keyValuePair.Value is Dictionary map) - CompareMaps(map, (Dictionary)second[keyValuePair.Key]); - else - keyValuePair.Value.Should().BeEquivalentTo(second[keyValuePair.Key]); - } + second.Should().ContainKey(keyValuePair.Key); + second[keyValuePair.Key].Should().BeOfType(keyValuePair.Value.GetType()); + if(keyValuePair.Value is Dictionary map) + CompareMaps(map, (Dictionary)second[keyValuePair.Key]); + else + keyValuePair.Value.Should().BeEquivalentTo(second[keyValuePair.Key]); } } } diff --git a/tests/GraphQL.Client.Serializer.Tests/NewtonsoftSerializerTest.cs b/tests/GraphQL.Client.Serializer.Tests/NewtonsoftSerializerTest.cs index 481a23c0..42e392c4 100644 --- a/tests/GraphQL.Client.Serializer.Tests/NewtonsoftSerializerTest.cs +++ b/tests/GraphQL.Client.Serializer.Tests/NewtonsoftSerializerTest.cs @@ -1,17 +1,16 @@ using GraphQL.Client.Serializer.Newtonsoft; using Newtonsoft.Json; -namespace GraphQL.Client.Serializer.Tests +namespace GraphQL.Client.Serializer.Tests; + +public class NewtonsoftSerializerTest : BaseSerializerTest { - public class NewtonsoftSerializerTest : BaseSerializerTest - { - public NewtonsoftSerializerTest() - : base(new NewtonsoftJsonSerializer(), new NewtonsoftJson.GraphQLSerializer()) { } - } + public NewtonsoftSerializerTest() + : base(new NewtonsoftJsonSerializer(), new NewtonsoftJson.GraphQLSerializer()) { } +} - public class NewtonsoftSerializeNoCamelCaseTest : BaseSerializeNoCamelCaseTest - { - public NewtonsoftSerializeNoCamelCaseTest() - : base(new NewtonsoftJsonSerializer(new JsonSerializerSettings { Converters = { new ConstantCaseEnumConverter() } }), new NewtonsoftJson.GraphQLSerializer()) { } - } +public class NewtonsoftSerializeNoCamelCaseTest : BaseSerializeNoCamelCaseTest +{ + public NewtonsoftSerializeNoCamelCaseTest() + : base(new NewtonsoftJsonSerializer(new JsonSerializerSettings { Converters = { new ConstantCaseEnumConverter() } }), new NewtonsoftJson.GraphQLSerializer()) { } } diff --git a/tests/GraphQL.Client.Serializer.Tests/SystemTextJsonSerializerTests.cs b/tests/GraphQL.Client.Serializer.Tests/SystemTextJsonSerializerTests.cs index d5e04733..bece29bd 100644 --- a/tests/GraphQL.Client.Serializer.Tests/SystemTextJsonSerializerTests.cs +++ b/tests/GraphQL.Client.Serializer.Tests/SystemTextJsonSerializerTests.cs @@ -2,17 +2,16 @@ using System.Text.Json.Serialization; using GraphQL.Client.Serializer.SystemTextJson; -namespace GraphQL.Client.Serializer.Tests +namespace GraphQL.Client.Serializer.Tests; + +public class SystemTextJsonSerializerTests : BaseSerializerTest { - public class SystemTextJsonSerializerTests : BaseSerializerTest - { - public SystemTextJsonSerializerTests() - : base(new SystemTextJsonSerializer(), new GraphQL.SystemTextJson.GraphQLSerializer()) { } - } + public SystemTextJsonSerializerTests() + : base(new SystemTextJsonSerializer(), new GraphQL.SystemTextJson.GraphQLSerializer()) { } +} - public class SystemTextJsonSerializeNoCamelCaseTest : BaseSerializeNoCamelCaseTest - { - public SystemTextJsonSerializeNoCamelCaseTest() - : base(new SystemTextJsonSerializer(new JsonSerializerOptions { Converters = { new JsonStringEnumConverter(new ConstantCaseJsonNamingPolicy(), false)}}.SetupImmutableConverter()), new GraphQL.SystemTextJson.GraphQLSerializer()) { } - } +public class SystemTextJsonSerializeNoCamelCaseTest : BaseSerializeNoCamelCaseTest +{ + public SystemTextJsonSerializeNoCamelCaseTest() + : base(new SystemTextJsonSerializer(new JsonSerializerOptions { Converters = { new JsonStringEnumConverter(new ConstantCaseJsonNamingPolicy(), false)}}.SetupImmutableConverter()), new GraphQL.SystemTextJson.GraphQLSerializer()) { } } diff --git a/tests/GraphQL.Client.Serializer.Tests/TestData/DeserializeResponseTestData.cs b/tests/GraphQL.Client.Serializer.Tests/TestData/DeserializeResponseTestData.cs index 31d02eed..6b388483 100644 --- a/tests/GraphQL.Client.Serializer.Tests/TestData/DeserializeResponseTestData.cs +++ b/tests/GraphQL.Client.Serializer.Tests/TestData/DeserializeResponseTestData.cs @@ -1,46 +1,46 @@ using System.Collections; using System.Collections.Generic; -namespace GraphQL.Client.Serializer.Tests.TestData +namespace GraphQL.Client.Serializer.Tests.TestData; + +public class DeserializeResponseTestData : IEnumerable { - public class DeserializeResponseTestData : IEnumerable + public IEnumerator GetEnumerator() { - public IEnumerator GetEnumerator() - { - // object array structure: - // [0]: input json - // [1]: expected deserialized response + // object array structure: + // [0]: input json + // [1]: expected deserialized response - yield return new object[] { - "{\"errors\":[{\"message\":\"Throttled\",\"extensions\":{\"code\":\"THROTTLED\",\"documentation\":\"https://help.shopify.com/api/graphql-admin-api/graphql-admin-api-rate-limits\"}}],\"extensions\":{\"cost\":{\"requestedQueryCost\":992,\"actualQueryCost\":null,\"throttleStatus\":{\"maximumAvailable\":1000,\"currentlyAvailable\":632,\"restoreRate\":50}}}}", - new GraphQLResponse { - Data = null, - Errors = new[] { - new GraphQLError { - Message = "Throttled", - Extensions = new Map { - {"code", "THROTTLED" }, - {"documentation", "https://help.shopify.com/api/graphql-admin-api/graphql-admin-api-rate-limits" } - } + yield return new object[] { + "{\"errors\":[{\"message\":\"Throttled\",\"extensions\":{\"code\":\"THROTTLED\",\"documentation\":\"https://help.shopify.com/api/graphql-admin-api/graphql-admin-api-rate-limits\"}}],\"extensions\":{\"cost\":{\"requestedQueryCost\":992,\"actualQueryCost\":null,\"throttleStatus\":{\"maximumAvailable\":1000,\"currentlyAvailable\":632,\"restoreRate\":50}}}}", + new GraphQLResponse { + Data = null, + Errors = new[] { + new GraphQLError { + Message = "Throttled", + Extensions = new Map { + {"code", "THROTTLED" }, + {"documentation", "https://help.shopify.com/api/graphql-admin-api/graphql-admin-api-rate-limits" } } - }, - Extensions = new Map { - {"cost", new Dictionary { - {"requestedQueryCost", 992}, - {"actualQueryCost", null}, - {"throttleStatus", new Dictionary { - {"maximumAvailable", 1000}, - {"currentlyAvailable", 632}, - {"restoreRate", 50} - }} - }} } + }, + Extensions = new Map { + {"cost", new Dictionary { + {"requestedQueryCost", 992}, + {"actualQueryCost", null}, + {"throttleStatus", new Dictionary { + {"maximumAvailable", 1000}, + {"currentlyAvailable", 632}, + {"restoreRate", 50} + }} + }} } - }; + } + }; - yield return new object[] - { - @"{ + yield return new object[] + { + @"{ ""errors"": [ { ""message"": ""Name for character with ID 1002 could not be fetched."", @@ -78,65 +78,64 @@ public IEnumerator GetEnumerator() } } }", - NewAnonymouslyTypedGraphQLResponse(new + NewAnonymouslyTypedGraphQLResponse(new + { + hero = new { - hero = new + name = "R2-D2", + heroFriends = new List { - name = "R2-D2", - heroFriends = new List - { - new Friend {Id = "1000", Name = "Luke Skywalker"}, - new Friend {Id = "1002", Name = null}, - new Friend {Id = "1003", Name = "Leia Organa"} - } - } - }, - new[] { - new GraphQLError { - Message = "Name for character with ID 1002 could not be fetched.", - Locations = new [] { new GraphQLLocation{Line = 6, Column = 7 }}, - Path = new ErrorPath{"hero", "heroFriends", 1, "name"} + new Friend {Id = "1000", Name = "Luke Skywalker"}, + new Friend {Id = "1002", Name = null}, + new Friend {Id = "1003", Name = "Leia Organa"} } - }) - }; + } + }, + new[] { + new GraphQLError { + Message = "Name for character with ID 1002 could not be fetched.", + Locations = new [] { new GraphQLLocation{Line = 6, Column = 7 }}, + Path = new ErrorPath{"hero", "heroFriends", 1, "name"} + } + }) + }; - // add test for github issue #230 : https://github.com/graphql-dotnet/graphql-client/issues/230 - yield return new object[] { - "{\"data\":{\"getMyModelType\":{\"id\":\"foo\",\"title\":\"The best Foo movie!\"}}}", - new GraphQLResponse { - Data = new GetMyModelTypeResponse + // add test for github issue #230 : https://github.com/graphql-dotnet/graphql-client/issues/230 + yield return new object[] { + "{\"data\":{\"getMyModelType\":{\"id\":\"foo\",\"title\":\"The best Foo movie!\"}}}", + new GraphQLResponse { + Data = new GetMyModelTypeResponse + { + getMyModelType = new Movie { - getMyModelType = new Movie - { - id = "foo", - title = "The best Foo movie!" - } - }, - } - }; - } + id = "foo", + title = "The best Foo movie!" + } + }, + } + }; + } - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - private GraphQLResponse NewAnonymouslyTypedGraphQLResponse(T data, GraphQLError[]? errors = null, Map? extensions = null) - => new GraphQLResponse {Data = data, Errors = errors, Extensions = extensions}; - } + private GraphQLResponse NewAnonymouslyTypedGraphQLResponse(T data, GraphQLError[]? errors = null, Map? extensions = null) + => new GraphQLResponse {Data = data, Errors = errors, Extensions = extensions}; +} - public class Friend - { - public string Id { get; set; } - public string? Name { get; set; } - } +public class Friend +{ + public string Id { get; set; } + public string? Name { get; set; } +} - public class GetMyModelTypeResponse - { - //--- Properties --- - public Movie getMyModelType { get; set; } - } - public class Movie - { - //--- Properties --- - public string id { get; set; } - public string title { get; set; } - } +public class GetMyModelTypeResponse +{ + //--- Properties --- + public Movie getMyModelType { get; set; } +} +public class Movie +{ + //--- Properties --- + public string id { get; set; } + public string title { get; set; } } diff --git a/tests/GraphQL.Client.Serializer.Tests/TestData/SerializeToBytesTestData.cs b/tests/GraphQL.Client.Serializer.Tests/TestData/SerializeToBytesTestData.cs index 3b581cc5..977ea5af 100644 --- a/tests/GraphQL.Client.Serializer.Tests/TestData/SerializeToBytesTestData.cs +++ b/tests/GraphQL.Client.Serializer.Tests/TestData/SerializeToBytesTestData.cs @@ -2,31 +2,30 @@ using System.Collections.Generic; using GraphQL.Client.Abstractions.Websocket; -namespace GraphQL.Client.Serializer.Tests.TestData +namespace GraphQL.Client.Serializer.Tests.TestData; + +public class SerializeToBytesTestData : IEnumerable { - public class SerializeToBytesTestData : IEnumerable + public IEnumerator GetEnumerator() { - public IEnumerator GetEnumerator() - { - yield return new object[] { - "{\"id\":\"1234567\",\"type\":\"start\",\"payload\":{\"query\":\"simplequerystring\",\"variables\":null,\"operationName\":null,\"extensions\":null}}", - new GraphQLWebSocketRequest { - Id = "1234567", - Type = GraphQLWebSocketMessageType.GQL_START, - Payload = new GraphQLRequest("simplequerystring") - } - }; - yield return new object[] { - "{\"id\":\"34476567\",\"type\":\"start\",\"payload\":{\"query\":\"simplequerystring\",\"variables\":{\"camelCaseProperty\":\"camelCase\",\"PascalCaseProperty\":\"PascalCase\"},\"operationName\":null,\"extensions\":null}}", - new GraphQLWebSocketRequest { - Id = "34476567", - Type = GraphQLWebSocketMessageType.GQL_START, - Payload = new GraphQLRequest("simple query string", new { camelCaseProperty = "camelCase", PascalCaseProperty = "PascalCase"}) - } - - }; - } + yield return new object[] { + "{\"id\":\"1234567\",\"type\":\"start\",\"payload\":{\"query\":\"simplequerystring\",\"variables\":null,\"operationName\":null,\"extensions\":null}}", + new GraphQLWebSocketRequest { + Id = "1234567", + Type = GraphQLWebSocketMessageType.GQL_START, + Payload = new GraphQLRequest("simplequerystring") + } + }; + yield return new object[] { + "{\"id\":\"34476567\",\"type\":\"start\",\"payload\":{\"query\":\"simplequerystring\",\"variables\":{\"camelCaseProperty\":\"camelCase\",\"PascalCaseProperty\":\"PascalCase\"},\"operationName\":null,\"extensions\":null}}", + new GraphQLWebSocketRequest { + Id = "34476567", + Type = GraphQLWebSocketMessageType.GQL_START, + Payload = new GraphQLRequest("simple query string", new { camelCaseProperty = "camelCase", PascalCaseProperty = "PascalCase"}) + } - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + }; } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } diff --git a/tests/GraphQL.Client.Serializer.Tests/TestData/SerializeToStringTestData.cs b/tests/GraphQL.Client.Serializer.Tests/TestData/SerializeToStringTestData.cs index 87634dad..395b9efc 100644 --- a/tests/GraphQL.Client.Serializer.Tests/TestData/SerializeToStringTestData.cs +++ b/tests/GraphQL.Client.Serializer.Tests/TestData/SerializeToStringTestData.cs @@ -3,40 +3,39 @@ using System.Collections.Generic; using System.Linq; -namespace GraphQL.Client.Serializer.Tests.TestData +namespace GraphQL.Client.Serializer.Tests.TestData; + +public class SerializeToStringTestData : IEnumerable { - public class SerializeToStringTestData : IEnumerable + public IEnumerator GetEnumerator() { - public IEnumerator GetEnumerator() - { - yield return new object[] { - "{\"query\":\"simplequerystring\",\"variables\":null,\"operationName\":null,\"extensions\":null}", - new GraphQLRequest("simple query string") - }; - yield return new object[] { - "{\"query\":\"simplequerystring\",\"variables\":{\"camelCaseProperty\":\"camelCase\",\"PascalCaseProperty\":\"PascalCase\"},\"operationName\":null,\"extensions\":null}", - new GraphQLRequest("simple query string", new { camelCaseProperty = "camelCase", PascalCaseProperty = "PascalCase"}) - }; - yield return new object[] { - "{\"query\":\"simplequerystring\",\"variables\":null,\"operationName\":null,\"extensions\":null,\"authentication\":\"an-authentication-token\"}", - new GraphQLRequest("simple query string"){{"authentication", "an-authentication-token"}} - }; - yield return new object[] { - "{\"query\":\"enumtest\",\"variables\":{\"enums\":[\"REGULAR\",\"PASCAL_CASE\",\"CAMEL_CASE\",\"LOWER\",\"UPPER\",\"CONSTANT_CASE\"]},\"operationName\":null,\"extensions\":null}", - new GraphQLRequest("enumtest", new { enums = Enum.GetValues(typeof(TestEnum)).Cast()}) - }; - } + yield return new object[] { + "{\"query\":\"simplequerystring\",\"variables\":null,\"operationName\":null,\"extensions\":null}", + new GraphQLRequest("simple query string") + }; + yield return new object[] { + "{\"query\":\"simplequerystring\",\"variables\":{\"camelCaseProperty\":\"camelCase\",\"PascalCaseProperty\":\"PascalCase\"},\"operationName\":null,\"extensions\":null}", + new GraphQLRequest("simple query string", new { camelCaseProperty = "camelCase", PascalCaseProperty = "PascalCase"}) + }; + yield return new object[] { + "{\"query\":\"simplequerystring\",\"variables\":null,\"operationName\":null,\"extensions\":null,\"authentication\":\"an-authentication-token\"}", + new GraphQLRequest("simple query string"){{"authentication", "an-authentication-token"}} + }; + yield return new object[] { + "{\"query\":\"enumtest\",\"variables\":{\"enums\":[\"REGULAR\",\"PASCAL_CASE\",\"CAMEL_CASE\",\"LOWER\",\"UPPER\",\"CONSTANT_CASE\"]},\"operationName\":null,\"extensions\":null}", + new GraphQLRequest("enumtest", new { enums = Enum.GetValues(typeof(TestEnum)).Cast()}) + }; + } - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - public enum TestEnum - { - Regular, - PascalCase, - camelCase, - lower, - UPPER, - CONSTANT_CASE - } + public enum TestEnum + { + Regular, + PascalCase, + camelCase, + lower, + UPPER, + CONSTANT_CASE } } diff --git a/tests/GraphQL.Client.Tests.Common/Chat/AddMessageMutationResult.cs b/tests/GraphQL.Client.Tests.Common/Chat/AddMessageMutationResult.cs index 70a51a72..b2c551ce 100644 --- a/tests/GraphQL.Client.Tests.Common/Chat/AddMessageMutationResult.cs +++ b/tests/GraphQL.Client.Tests.Common/Chat/AddMessageMutationResult.cs @@ -1,12 +1,11 @@ -namespace GraphQL.Client.Tests.Common.Chat +namespace GraphQL.Client.Tests.Common.Chat; + +public class AddMessageMutationResult { - public class AddMessageMutationResult - { - public AddMessageContent AddMessage { get; set; } + public AddMessageContent AddMessage { get; set; } - public class AddMessageContent - { - public string Content { get; set; } - } + public class AddMessageContent + { + public string Content { get; set; } } } diff --git a/tests/GraphQL.Client.Tests.Common/Chat/AddMessageVariables.cs b/tests/GraphQL.Client.Tests.Common/Chat/AddMessageVariables.cs index 916054e8..8f55c02c 100644 --- a/tests/GraphQL.Client.Tests.Common/Chat/AddMessageVariables.cs +++ b/tests/GraphQL.Client.Tests.Common/Chat/AddMessageVariables.cs @@ -1,16 +1,15 @@ -namespace GraphQL.Client.Tests.Common.Chat +namespace GraphQL.Client.Tests.Common.Chat; + +public class AddMessageVariables { - public class AddMessageVariables - { - public AddMessageInput Input { get; set; } + public AddMessageInput Input { get; set; } - public class AddMessageInput - { - public string FromId { get; set; } + public class AddMessageInput + { + public string FromId { get; set; } - public string Content { get; set; } + public string Content { get; set; } - public string SentAt { get; set; } - } + public string SentAt { get; set; } } } diff --git a/tests/GraphQL.Client.Tests.Common/Chat/GraphQLClientChatExtensions.cs b/tests/GraphQL.Client.Tests.Common/Chat/GraphQLClientChatExtensions.cs index 47edfc28..fa10984a 100644 --- a/tests/GraphQL.Client.Tests.Common/Chat/GraphQLClientChatExtensions.cs +++ b/tests/GraphQL.Client.Tests.Common/Chat/GraphQLClientChatExtensions.cs @@ -1,46 +1,45 @@ using GraphQL.Client.Abstractions; -namespace GraphQL.Client.Tests.Common.Chat +namespace GraphQL.Client.Tests.Common.Chat; + +public static class GraphQLClientChatExtensions { - public static class GraphQLClientChatExtensions - { - public const string ADD_MESSAGE_QUERY = + public const string ADD_MESSAGE_QUERY = @"mutation($input: MessageInputType){ addMessage(message: $input){ content } }"; - public static Task> AddMessageAsync(this IGraphQLClient client, string message) + public static Task> AddMessageAsync(this IGraphQLClient client, string message) + { + var variables = new AddMessageVariables { - var variables = new AddMessageVariables + Input = new AddMessageVariables.AddMessageInput { - Input = new AddMessageVariables.AddMessageInput - { - FromId = "2", - Content = message, - SentAt = DateTime.Now.ToString("s") - } - }; + FromId = "2", + Content = message, + SentAt = DateTime.Now.ToString("s") + } + }; - var graphQLRequest = new GraphQLRequest(ADD_MESSAGE_QUERY, variables); - return client.SendMutationAsync(graphQLRequest); - } + var graphQLRequest = new GraphQLRequest(ADD_MESSAGE_QUERY, variables); + return client.SendMutationAsync(graphQLRequest); + } - public static Task> JoinDeveloperUser(this IGraphQLClient client) - { - var graphQLRequest = new GraphQLRequest(@" + public static Task> JoinDeveloperUser(this IGraphQLClient client) + { + var graphQLRequest = new GraphQLRequest(@" mutation($userId: String){ join(userId: $userId){ displayName id } }", - new - { - userId = "1" - }); - return client.SendMutationAsync(graphQLRequest); - } + new + { + userId = "1" + }); + return client.SendMutationAsync(graphQLRequest); } } diff --git a/tests/GraphQL.Client.Tests.Common/Chat/JoinDeveloperMutationResult.cs b/tests/GraphQL.Client.Tests.Common/Chat/JoinDeveloperMutationResult.cs index c9910444..d55bf0ca 100644 --- a/tests/GraphQL.Client.Tests.Common/Chat/JoinDeveloperMutationResult.cs +++ b/tests/GraphQL.Client.Tests.Common/Chat/JoinDeveloperMutationResult.cs @@ -1,14 +1,13 @@ -namespace GraphQL.Client.Tests.Common.Chat +namespace GraphQL.Client.Tests.Common.Chat; + +public class JoinDeveloperMutationResult { - public class JoinDeveloperMutationResult - { - public JoinContent Join { get; set; } + public JoinContent Join { get; set; } - public class JoinContent - { - public string DisplayName { get; set; } + public class JoinContent + { + public string DisplayName { get; set; } - public string Id { get; set; } - } + public string Id { get; set; } } } diff --git a/tests/GraphQL.Client.Tests.Common/Chat/Schema/CapitalizedFieldsGraphType.cs b/tests/GraphQL.Client.Tests.Common/Chat/Schema/CapitalizedFieldsGraphType.cs index ecfeaf00..a1f185fb 100644 --- a/tests/GraphQL.Client.Tests.Common/Chat/Schema/CapitalizedFieldsGraphType.cs +++ b/tests/GraphQL.Client.Tests.Common/Chat/Schema/CapitalizedFieldsGraphType.cs @@ -1,16 +1,15 @@ using GraphQL.Types; -namespace GraphQL.Client.Tests.Common.Chat.Schema +namespace GraphQL.Client.Tests.Common.Chat.Schema; + +public class CapitalizedFieldsGraphType : ObjectGraphType { - public class CapitalizedFieldsGraphType : ObjectGraphType + public CapitalizedFieldsGraphType() { - public CapitalizedFieldsGraphType() - { - Name = "CapitalizedFields"; + Name = "CapitalizedFields"; - Field() - .Name("StringField") - .Resolve(context => "hello world"); - } + Field() + .Name("StringField") + .Resolve(context => "hello world"); } } diff --git a/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatMutation.cs b/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatMutation.cs index b005ebc2..788eaaba 100644 --- a/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatMutation.cs +++ b/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatMutation.cs @@ -1,42 +1,41 @@ using GraphQL.Types; -namespace GraphQL.Client.Tests.Common.Chat.Schema +namespace GraphQL.Client.Tests.Common.Chat.Schema; + +public class ChatMutation : ObjectGraphType { - public class ChatMutation : ObjectGraphType + public ChatMutation(IChat chat) { - public ChatMutation(IChat chat) - { - Field("addMessage", - arguments: new QueryArguments( - new QueryArgument { Name = "message" } - ), - resolve: context => - { - var receivedMessage = context.GetArgument("message"); - var message = chat.AddMessage(receivedMessage); - return message; - }); + Field("addMessage", + arguments: new QueryArguments( + new QueryArgument { Name = "message" } + ), + resolve: context => + { + var receivedMessage = context.GetArgument("message"); + 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; - }); - } + 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 +public class MessageInputType : InputObjectGraphType +{ + public MessageInputType() { - public MessageInputType() - { - Field("fromId"); - Field("content"); - Field("sentAt"); - } + Field("fromId"); + Field("content"); + Field("sentAt"); } } diff --git a/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatQuery.cs b/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatQuery.cs index 5645beb8..854a4a75 100644 --- a/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatQuery.cs +++ b/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatQuery.cs @@ -1,43 +1,42 @@ using GraphQL.Types; -namespace GraphQL.Client.Tests.Common.Chat.Schema +namespace GraphQL.Client.Tests.Common.Chat.Schema; + +public class ChatQuery : ObjectGraphType { - public class ChatQuery : ObjectGraphType + public static readonly Dictionary TestExtensions = new Dictionary { + {"extension1", "hello world"}, + {"another extension", 4711}, + {"long", 19942590700} + }; + + // properties for unit testing + + public readonly ManualResetEventSlim LongRunningQueryBlocker = new ManualResetEventSlim(); + public readonly ManualResetEventSlim WaitingOnQueryBlocker = new ManualResetEventSlim(); + + public ChatQuery(IChat chat) { - public static readonly Dictionary TestExtensions = new Dictionary { - {"extension1", "hello world"}, - {"another extension", 4711}, - {"long", 19942590700} - }; - - // properties for unit testing - - public readonly ManualResetEventSlim LongRunningQueryBlocker = new ManualResetEventSlim(); - public readonly ManualResetEventSlim WaitingOnQueryBlocker = new ManualResetEventSlim(); - - public ChatQuery(IChat chat) - { - Name = "ChatQuery"; - - Field>("messages", resolve: context => chat.AllMessages.Take(100)); - - Field() - .Name("extensionsTest") - .Resolve(context => - { - context.Errors.Add(new ExecutionError("this error contains extension fields", TestExtensions)); - return null; - }); - - Field() - .Name("longRunning") - .Resolve(context => - { - WaitingOnQueryBlocker.Set(); - LongRunningQueryBlocker.Wait(); - WaitingOnQueryBlocker.Reset(); - return "finally returned"; - }); - } + Name = "ChatQuery"; + + Field>("messages", resolve: context => chat.AllMessages.Take(100)); + + Field() + .Name("extensionsTest") + .Resolve(context => + { + context.Errors.Add(new ExecutionError("this error contains extension fields", TestExtensions)); + return null; + }); + + Field() + .Name("longRunning") + .Resolve(context => + { + WaitingOnQueryBlocker.Set(); + LongRunningQueryBlocker.Wait(); + WaitingOnQueryBlocker.Reset(); + return "finally returned"; + }); } } diff --git a/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatSchema.cs b/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatSchema.cs index d606cdbd..9cacc7d4 100644 --- a/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatSchema.cs +++ b/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatSchema.cs @@ -1,15 +1,14 @@ using Microsoft.Extensions.DependencyInjection; -namespace GraphQL.Client.Tests.Common.Chat.Schema +namespace GraphQL.Client.Tests.Common.Chat.Schema; + +public class ChatSchema : Types.Schema { - public class ChatSchema : Types.Schema + public ChatSchema(IServiceProvider services) + : base(services) { - public ChatSchema(IServiceProvider services) - : base(services) - { - Query = services.GetRequiredService(); - Mutation = services.GetRequiredService(); - Subscription = services.GetRequiredService(); - } + Query = services.GetRequiredService(); + Mutation = services.GetRequiredService(); + Subscription = services.GetRequiredService(); } } diff --git a/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatSubscriptions.cs b/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatSubscriptions.cs index 58dcd240..3a4f62e1 100644 --- a/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatSubscriptions.cs +++ b/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatSubscriptions.cs @@ -4,92 +4,91 @@ using GraphQL.Server.Transports.Subscriptions.Abstractions; using GraphQL.Types; -namespace GraphQL.Client.Tests.Common.Chat.Schema +namespace GraphQL.Client.Tests.Common.Chat.Schema; + +public class ChatSubscriptions : ObjectGraphType { - public class ChatSubscriptions : ObjectGraphType + private readonly IChat _chat; + + public ChatSubscriptions(IChat chat) { - private readonly IChat _chat; + _chat = chat; + AddField(new FieldType + { + Name = "messageAdded", + Type = typeof(MessageType), + Resolver = new FuncFieldResolver(ResolveMessage), + StreamResolver = new SourceStreamResolver(Subscribe) + }); + + AddField(new FieldType + { + Name = "contentAdded", + Type = typeof(MessageType), + Resolver = new FuncFieldResolver(ResolveMessage), + StreamResolver = new SourceStreamResolver(Subscribe) + }); - public ChatSubscriptions(IChat chat) + AddField(new FieldType { - _chat = chat; - AddField(new FieldType - { - Name = "messageAdded", - Type = typeof(MessageType), - Resolver = new FuncFieldResolver(ResolveMessage), - StreamResolver = new SourceStreamResolver(Subscribe) - }); - - AddField(new FieldType - { - Name = "contentAdded", - Type = typeof(MessageType), - Resolver = new FuncFieldResolver(ResolveMessage), - StreamResolver = new SourceStreamResolver(Subscribe) - }); - - AddField(new FieldType - { - Name = "messageAddedByUser", - Arguments = new QueryArguments( - new QueryArgument> { Name = "id" } - ), - Type = typeof(MessageType), - Resolver = new FuncFieldResolver(ResolveMessage), - StreamResolver = new SourceStreamResolver(SubscribeById) - }); - - AddField(new FieldType - { - Name = "userJoined", - Type = typeof(MessageFromType), - Resolver = new FuncFieldResolver(context => context.Source as MessageFrom), - StreamResolver = new SourceStreamResolver(context => _chat.UserJoined()) - }); - - - AddField(new FieldType - { - Name = "failImmediately", - Type = typeof(MessageType), - Resolver = new FuncFieldResolver(ResolveMessage), - StreamResolver = new SourceStreamResolver((Func>)(context => throw new NotSupportedException("this is supposed to fail"))) - }); - } - - private IObservable SubscribeById(IResolveFieldContext context) + Name = "messageAddedByUser", + Arguments = new QueryArguments( + new QueryArgument> { Name = "id" } + ), + Type = typeof(MessageType), + Resolver = new FuncFieldResolver(ResolveMessage), + StreamResolver = new SourceStreamResolver(SubscribeById) + }); + + AddField(new FieldType { - var messageContext = (MessageHandlingContext)context.UserContext; - var user = messageContext.Get("user"); + Name = "userJoined", + Type = typeof(MessageFromType), + Resolver = new FuncFieldResolver(context => context.Source as MessageFrom), + StreamResolver = new SourceStreamResolver(context => _chat.UserJoined()) + }); - var sub = "Anonymous"; - if (user != null) - sub = user.Claims.FirstOrDefault(c => c.Type == "sub")?.Value; - var messages = _chat.Messages(sub); + AddField(new FieldType + { + Name = "failImmediately", + Type = typeof(MessageType), + Resolver = new FuncFieldResolver(ResolveMessage), + StreamResolver = new SourceStreamResolver((Func>)(context => throw new NotSupportedException("this is supposed to fail"))) + }); + } - var id = context.GetArgument("id"); - return messages.Where(message => message.From.Id == id); - } + private IObservable SubscribeById(IResolveFieldContext context) + { + var messageContext = (MessageHandlingContext)context.UserContext; + var user = messageContext.Get("user"); - private Message ResolveMessage(IResolveFieldContext context) - { - var message = context.Source as Message; + var sub = "Anonymous"; + if (user != null) + sub = user.Claims.FirstOrDefault(c => c.Type == "sub")?.Value; - return message; - } + var messages = _chat.Messages(sub); - private IObservable Subscribe(IResolveFieldContext context) - { - var messageContext = (MessageHandlingContext)context.UserContext; - var user = messageContext.Get("user"); + var id = context.GetArgument("id"); + return messages.Where(message => message.From.Id == id); + } + + private Message ResolveMessage(IResolveFieldContext context) + { + var message = context.Source as Message; + + return message; + } + + private IObservable Subscribe(IResolveFieldContext context) + { + var messageContext = (MessageHandlingContext)context.UserContext; + var user = messageContext.Get("user"); - var sub = "Anonymous"; - if (user != null) - sub = user.Claims.FirstOrDefault(c => c.Type == "sub")?.Value; + var sub = "Anonymous"; + if (user != null) + sub = user.Claims.FirstOrDefault(c => c.Type == "sub")?.Value; - return _chat.Messages(sub); - } + return _chat.Messages(sub); } } diff --git a/tests/GraphQL.Client.Tests.Common/Chat/Schema/IChat.cs b/tests/GraphQL.Client.Tests.Common/Chat/Schema/IChat.cs index bfbdeb32..a00a35dc 100644 --- a/tests/GraphQL.Client.Tests.Common/Chat/Schema/IChat.cs +++ b/tests/GraphQL.Client.Tests.Common/Chat/Schema/IChat.cs @@ -4,108 +4,107 @@ using System.Reactive.Linq; using System.Reactive.Subjects; -namespace GraphQL.Client.Tests.Common.Chat.Schema +namespace GraphQL.Client.Tests.Common.Chat.Schema; + +public interface IChat { - public interface IChat - { - ConcurrentStack AllMessages { get; } + ConcurrentStack AllMessages { get; } - Message AddMessage(Message message); + Message AddMessage(Message message); - MessageFrom Join(string userId); + MessageFrom Join(string userId); - IObservable Messages(string user); - IObservable UserJoined(); + IObservable Messages(string user); + IObservable UserJoined(); - Message AddMessage(ReceivedMessage message); - } + Message AddMessage(ReceivedMessage message); +} - public class Chat : IChat - { - private readonly ISubject _messageStream = new ReplaySubject(1); - private readonly ISubject _userJoined = new Subject(); +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 { - AllMessages = new ConcurrentStack(); - Users = new ConcurrentDictionary - { - ["1"] = "developer", - ["2"] = "tester" - }; - } + ["1"] = "developer", + ["2"] = "tester" + }; + } - public ConcurrentDictionary Users { get; private set; } + public ConcurrentDictionary Users { get; private set; } - public ConcurrentStack AllMessages { get; private set; } + public ConcurrentStack AllMessages { get; private set; } + + public Message AddMessage(ReceivedMessage message) + { + if (!Users.TryGetValue(message.FromId, out var displayName)) + { + displayName = "(unknown)"; + } - public Message AddMessage(ReceivedMessage message) + return AddMessage(new Message { - if (!Users.TryGetValue(message.FromId, out var displayName)) + Content = message.Content, + SentAt = message.SentAt, + From = new MessageFrom { - displayName = "(unknown)"; + DisplayName = displayName, + Id = message.FromId } + }); + } - return AddMessage(new Message - { - Content = message.Content, - SentAt = message.SentAt, - From = new MessageFrom - { - DisplayName = displayName, - Id = message.FromId - } - }); - } + public Message AddMessage(Message message) + { + AllMessages.Push(message); + _messageStream.OnNext(message); + return message; + } - public Message AddMessage(Message message) + public MessageFrom Join(string userId) + { + if (!Users.TryGetValue(userId, out var displayName)) { - AllMessages.Push(message); - _messageStream.OnNext(message); - return message; + displayName = "(unknown)"; } - public MessageFrom Join(string userId) + var joinedUser = new MessageFrom { - if (!Users.TryGetValue(userId, out var displayName)) - { - displayName = "(unknown)"; - } + Id = userId, + DisplayName = displayName + }; + + _userJoined.OnNext(joinedUser); + return joinedUser; + } - var joinedUser = new MessageFrom + public IObservable Messages(string user) => + Observable.Create(observer => + { + Debug.WriteLine($"creating messages stream for user '{user}' on thread {Thread.CurrentThread.ManagedThreadId}"); + return new CompositeDisposable { - Id = userId, - DisplayName = displayName + _messageStream.Select(message => + { + message.Sub = user; + return message; + }) + .Subscribe(observer), + Disposable.Create(() => Debug.WriteLine($"disposing messages stream for user '{user}' on thread {Thread.CurrentThread.ManagedThreadId}")) }; + }); - _userJoined.OnNext(joinedUser); - return joinedUser; - } + public void AddError(Exception exception) => _messageStream.OnError(exception); - public IObservable Messages(string user) => - Observable.Create(observer => - { - Debug.WriteLine($"creating messages stream for user '{user}' on thread {Thread.CurrentThread.ManagedThreadId}"); - return new CompositeDisposable - { - _messageStream.Select(message => - { - message.Sub = user; - return message; - }) - .Subscribe(observer), - Disposable.Create(() => Debug.WriteLine($"disposing messages stream for user '{user}' on thread {Thread.CurrentThread.ManagedThreadId}")) - }; - }); - - public void AddError(Exception exception) => _messageStream.OnError(exception); - - public IObservable UserJoined() => _userJoined.AsObservable(); - } + public IObservable UserJoined() => _userJoined.AsObservable(); +} - public class User - { - public string Id { get; set; } - public string Name { get; set; } - } +public class User +{ + public string Id { get; set; } + public string Name { get; set; } } diff --git a/tests/GraphQL.Client.Tests.Common/Chat/Schema/Message.cs b/tests/GraphQL.Client.Tests.Common/Chat/Schema/Message.cs index e344caee..aaf77218 100644 --- a/tests/GraphQL.Client.Tests.Common/Chat/Schema/Message.cs +++ b/tests/GraphQL.Client.Tests.Common/Chat/Schema/Message.cs @@ -1,13 +1,12 @@ -namespace GraphQL.Client.Tests.Common.Chat.Schema +namespace GraphQL.Client.Tests.Common.Chat.Schema; + +public class Message { - public class Message - { - public MessageFrom From { get; set; } + public MessageFrom From { get; set; } - public string Sub { get; set; } + public string Sub { get; set; } - public string Content { get; set; } + public string Content { get; set; } - public DateTime SentAt { get; set; } - } + public DateTime SentAt { get; set; } } diff --git a/tests/GraphQL.Client.Tests.Common/Chat/Schema/MessageFrom.cs b/tests/GraphQL.Client.Tests.Common/Chat/Schema/MessageFrom.cs index 203f979b..637d5198 100644 --- a/tests/GraphQL.Client.Tests.Common/Chat/Schema/MessageFrom.cs +++ b/tests/GraphQL.Client.Tests.Common/Chat/Schema/MessageFrom.cs @@ -1,9 +1,8 @@ -namespace GraphQL.Client.Tests.Common.Chat.Schema +namespace GraphQL.Client.Tests.Common.Chat.Schema; + +public class MessageFrom { - public class MessageFrom - { - public string Id { get; set; } + public string Id { get; set; } - public string DisplayName { get; set; } - } + public string DisplayName { get; set; } } diff --git a/tests/GraphQL.Client.Tests.Common/Chat/Schema/MessageFromType.cs b/tests/GraphQL.Client.Tests.Common/Chat/Schema/MessageFromType.cs index 045e473b..45cb68d7 100644 --- a/tests/GraphQL.Client.Tests.Common/Chat/Schema/MessageFromType.cs +++ b/tests/GraphQL.Client.Tests.Common/Chat/Schema/MessageFromType.cs @@ -1,13 +1,12 @@ using GraphQL.Types; -namespace GraphQL.Client.Tests.Common.Chat.Schema +namespace GraphQL.Client.Tests.Common.Chat.Schema; + +public class MessageFromType : ObjectGraphType { - public class MessageFromType : ObjectGraphType + public MessageFromType() { - public MessageFromType() - { - Field(o => o.Id); - Field(o => o.DisplayName); - } + Field(o => o.Id); + Field(o => o.DisplayName); } } diff --git a/tests/GraphQL.Client.Tests.Common/Chat/Schema/MessageType.cs b/tests/GraphQL.Client.Tests.Common/Chat/Schema/MessageType.cs index e72a0067..a1c8c837 100644 --- a/tests/GraphQL.Client.Tests.Common/Chat/Schema/MessageType.cs +++ b/tests/GraphQL.Client.Tests.Common/Chat/Schema/MessageType.cs @@ -1,21 +1,20 @@ using GraphQL.Types; -namespace GraphQL.Client.Tests.Common.Chat.Schema +namespace GraphQL.Client.Tests.Common.Chat.Schema; + +public class MessageType : ObjectGraphType { - public class MessageType : ObjectGraphType + public MessageType() { - public MessageType() - { - Field(o => o.Content); - Field(o => o.SentAt); - Field(o => o.Sub); - Field(o => o.From, false, typeof(MessageFromType)).Resolve(ResolveFrom); - } + Field(o => o.Content); + Field(o => o.SentAt); + Field(o => o.Sub); + Field(o => o.From, false, typeof(MessageFromType)).Resolve(ResolveFrom); + } - private MessageFrom ResolveFrom(IResolveFieldContext context) - { - var message = context.Source; - return message.From; - } + private MessageFrom ResolveFrom(IResolveFieldContext context) + { + var message = context.Source; + return message.From; } } diff --git a/tests/GraphQL.Client.Tests.Common/Chat/Schema/ReceivedMessage.cs b/tests/GraphQL.Client.Tests.Common/Chat/Schema/ReceivedMessage.cs index 95d7d295..78a675dc 100644 --- a/tests/GraphQL.Client.Tests.Common/Chat/Schema/ReceivedMessage.cs +++ b/tests/GraphQL.Client.Tests.Common/Chat/Schema/ReceivedMessage.cs @@ -1,11 +1,10 @@ -namespace GraphQL.Client.Tests.Common.Chat.Schema +namespace GraphQL.Client.Tests.Common.Chat.Schema; + +public class ReceivedMessage { - public class ReceivedMessage - { - public string FromId { get; set; } + public string FromId { get; set; } - public string Content { get; set; } + public string Content { get; set; } - public DateTime SentAt { get; set; } - } + public DateTime SentAt { get; set; } } diff --git a/tests/GraphQL.Client.Tests.Common/Common.cs b/tests/GraphQL.Client.Tests.Common/Common.cs index 269ea257..83fa8929 100644 --- a/tests/GraphQL.Client.Tests.Common/Common.cs +++ b/tests/GraphQL.Client.Tests.Common/Common.cs @@ -3,52 +3,51 @@ using GraphQL.Client.Tests.Common.StarWars.Types; using Microsoft.Extensions.DependencyInjection; -namespace GraphQL.Client.Tests.Common +namespace GraphQL.Client.Tests.Common; + +public static class Common { - public static class Common - { - public const string STAR_WARS_ENDPOINT = "/graphql/starwars"; - public const string CHAT_ENDPOINT = "/graphql/chat"; + public const string STAR_WARS_ENDPOINT = "/graphql/starwars"; + public const string CHAT_ENDPOINT = "/graphql/chat"; - public static StarWarsSchema GetStarWarsSchema() - { - var services = new ServiceCollection(); - services.AddStarWarsSchema(); - return services.BuildServiceProvider().GetRequiredService(); - } + public static StarWarsSchema GetStarWarsSchema() + { + var services = new ServiceCollection(); + services.AddStarWarsSchema(); + return services.BuildServiceProvider().GetRequiredService(); + } - public static ChatSchema GetChatSchema() - { - var services = new ServiceCollection(); - services.AddChatSchema(); - return services.BuildServiceProvider().GetRequiredService(); - } + public static ChatSchema GetChatSchema() + { + var services = new ServiceCollection(); + services.AddChatSchema(); + return services.BuildServiceProvider().GetRequiredService(); + } - public static void AddStarWarsSchema(this IServiceCollection services) - { - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - } + public static void AddStarWarsSchema(this IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + } - public static void AddChatSchema(this IServiceCollection services) - { - var chat = new Chat.Schema.Chat(); - services.AddSingleton(chat); - services.AddSingleton(chat); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - } + public static void AddChatSchema(this IServiceCollection services) + { + var chat = new Chat.Schema.Chat(); + services.AddSingleton(chat); + services.AddSingleton(chat); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); } } diff --git a/tests/GraphQL.Client.Tests.Common/Helpers/AvailableJsonSerializers.cs b/tests/GraphQL.Client.Tests.Common/Helpers/AvailableJsonSerializers.cs index dca19832..465a31a1 100644 --- a/tests/GraphQL.Client.Tests.Common/Helpers/AvailableJsonSerializers.cs +++ b/tests/GraphQL.Client.Tests.Common/Helpers/AvailableJsonSerializers.cs @@ -1,22 +1,21 @@ using System.Collections; using GraphQL.Client.Abstractions; -namespace GraphQL.Client.Tests.Common.Helpers +namespace GraphQL.Client.Tests.Common.Helpers; + +public class AvailableJsonSerializers : IEnumerable where TSerializerInterface : IGraphQLJsonSerializer { - public class AvailableJsonSerializers : IEnumerable where TSerializerInterface : IGraphQLJsonSerializer + public IEnumerator GetEnumerator() { - public IEnumerator GetEnumerator() - { - // try to find one in the assembly and assign that - var type = typeof(TSerializerInterface); - return AppDomain.CurrentDomain - .GetAssemblies() - .SelectMany(s => s.GetTypes()) - .Where(p => type.IsAssignableFrom(p) && !p.IsInterface && !p.IsAbstract) - .Select(serializerType => new object[] { Activator.CreateInstance(serializerType) }) - .GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + // try to find one in the assembly and assign that + var type = typeof(TSerializerInterface); + return AppDomain.CurrentDomain + .GetAssemblies() + .SelectMany(s => s.GetTypes()) + .Where(p => type.IsAssignableFrom(p) && !p.IsInterface && !p.IsAbstract) + .Select(serializerType => new object[] { Activator.CreateInstance(serializerType) }) + .GetEnumerator(); } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } diff --git a/tests/GraphQL.Client.Tests.Common/Helpers/CallbackMonitor.cs b/tests/GraphQL.Client.Tests.Common/Helpers/CallbackMonitor.cs index 3603041c..eaf2e10c 100644 --- a/tests/GraphQL.Client.Tests.Common/Helpers/CallbackMonitor.cs +++ b/tests/GraphQL.Client.Tests.Common/Helpers/CallbackMonitor.cs @@ -3,90 +3,89 @@ using FluentAssertions.Execution; using FluentAssertions.Primitives; -namespace GraphQL.Client.Tests.Common.Helpers +namespace GraphQL.Client.Tests.Common.Helpers; + +public class CallbackMonitor { - public class CallbackMonitor - { - private readonly ManualResetEventSlim _callbackInvoked = new ManualResetEventSlim(); + private readonly ManualResetEventSlim _callbackInvoked = new ManualResetEventSlim(); - /// - /// The timeout for . Defaults to 1 second. - /// - public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(1); + /// + /// The timeout for . Defaults to 1 second. + /// + public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(1); - /// - /// Indicates that an update has been received since the last - /// - public bool CallbackInvoked => _callbackInvoked.IsSet; + /// + /// Indicates that an update has been received since the last + /// + public bool CallbackInvoked => _callbackInvoked.IsSet; - /// - /// The last payload which was received. - /// - public T LastPayload { get; private set; } + /// + /// The last payload which was received. + /// + public T LastPayload { get; private set; } - public void Invoke(T param) - { - LastPayload = param; - Debug.WriteLine($"CallbackMonitor invoke handler thread id: {Thread.CurrentThread.ManagedThreadId}"); - _callbackInvoked.Set(); - } + public void Invoke(T param) + { + LastPayload = param; + Debug.WriteLine($"CallbackMonitor invoke handler thread id: {Thread.CurrentThread.ManagedThreadId}"); + _callbackInvoked.Set(); + } - /// - /// Resets the tester class. Should be called before triggering the potential update - /// - public void Reset() - { - LastPayload = default; - _callbackInvoked.Reset(); - } + /// + /// Resets the tester class. Should be called before triggering the potential update + /// + public void Reset() + { + LastPayload = default; + _callbackInvoked.Reset(); + } - public CallbackAssertions Should() => new CallbackAssertions(this); + public CallbackAssertions Should() => new CallbackAssertions(this); - public class CallbackAssertions : ReferenceTypeAssertions, CallbackAssertions> - { - public CallbackAssertions(CallbackMonitor tester): base(tester) - { } + public class CallbackAssertions : ReferenceTypeAssertions, CallbackAssertions> + { + public CallbackAssertions(CallbackMonitor tester): base(tester) + { } - protected override string Identifier => "callback"; + protected override string Identifier => "callback"; - public AndWhichConstraint, TPayload> HaveBeenInvokedWithPayload(TimeSpan timeout, - string because = "", params object[] becauseArgs) - { - Execute.Assertion - .BecauseOf(because, becauseArgs) - .Given(() => - { - Debug.WriteLine($"HaveBeenInvokedWithPayload thread id: {Thread.CurrentThread.ManagedThreadId}"); - return Subject._callbackInvoked.Wait(timeout); - }) - .ForCondition(isSet => isSet) - .FailWith("Expected {context:callback} to be invoked{reason}, but did not receive a call within {0}", timeout); + public AndWhichConstraint, TPayload> HaveBeenInvokedWithPayload(TimeSpan timeout, + string because = "", params object[] becauseArgs) + { + Execute.Assertion + .BecauseOf(because, becauseArgs) + .Given(() => + { + Debug.WriteLine($"HaveBeenInvokedWithPayload thread id: {Thread.CurrentThread.ManagedThreadId}"); + return Subject._callbackInvoked.Wait(timeout); + }) + .ForCondition(isSet => isSet) + .FailWith("Expected {context:callback} to be invoked{reason}, but did not receive a call within {0}", timeout); - Subject._callbackInvoked.Reset(); - return new AndWhichConstraint, TPayload>(this, Subject.LastPayload); - } - public AndWhichConstraint, TPayload> HaveBeenInvokedWithPayload(string because = "", params object[] becauseArgs) - => HaveBeenInvokedWithPayload(Subject.Timeout, because, becauseArgs); + Subject._callbackInvoked.Reset(); + return new AndWhichConstraint, TPayload>(this, Subject.LastPayload); + } + public AndWhichConstraint, TPayload> HaveBeenInvokedWithPayload(string because = "", params object[] becauseArgs) + => HaveBeenInvokedWithPayload(Subject.Timeout, because, becauseArgs); - public AndConstraint> HaveBeenInvoked(TimeSpan timeout, string because = "", params object[] becauseArgs) - => HaveBeenInvokedWithPayload(timeout, because, becauseArgs); - public AndConstraint> HaveBeenInvoked(string because = "", params object[] becauseArgs) - => HaveBeenInvokedWithPayload(Subject.Timeout, because, becauseArgs); + public AndConstraint> HaveBeenInvoked(TimeSpan timeout, string because = "", params object[] becauseArgs) + => HaveBeenInvokedWithPayload(timeout, because, becauseArgs); + public AndConstraint> HaveBeenInvoked(string because = "", params object[] becauseArgs) + => HaveBeenInvokedWithPayload(Subject.Timeout, because, becauseArgs); - public AndConstraint> NotHaveBeenInvoked(TimeSpan timeout, - string because = "", params object[] becauseArgs) - { - Execute.Assertion - .BecauseOf(because, becauseArgs) - .Given(() => Subject._callbackInvoked.Wait(timeout)) - .ForCondition(isSet => !isSet) - .FailWith("Expected {context:callback} to not be invoked{reason}, but did receive a call: {0}", Subject.LastPayload); + public AndConstraint> NotHaveBeenInvoked(TimeSpan timeout, + string because = "", params object[] becauseArgs) + { + Execute.Assertion + .BecauseOf(because, becauseArgs) + .Given(() => Subject._callbackInvoked.Wait(timeout)) + .ForCondition(isSet => !isSet) + .FailWith("Expected {context:callback} to not be invoked{reason}, but did receive a call: {0}", Subject.LastPayload); - Subject._callbackInvoked.Reset(); - return new AndConstraint>(this); - } - public AndConstraint> NotHaveBeenInvoked(string because = "", params object[] becauseArgs) - => NotHaveBeenInvoked(TimeSpan.FromMilliseconds(100), because, becauseArgs); + Subject._callbackInvoked.Reset(); + return new AndConstraint>(this); } + public AndConstraint> NotHaveBeenInvoked(string because = "", params object[] becauseArgs) + => NotHaveBeenInvoked(TimeSpan.FromMilliseconds(100), because, becauseArgs); } } diff --git a/tests/GraphQL.Client.Tests.Common/Helpers/ConcurrentTaskWrapper.cs b/tests/GraphQL.Client.Tests.Common/Helpers/ConcurrentTaskWrapper.cs index 6d957c38..c49849ed 100644 --- a/tests/GraphQL.Client.Tests.Common/Helpers/ConcurrentTaskWrapper.cs +++ b/tests/GraphQL.Client.Tests.Common/Helpers/ConcurrentTaskWrapper.cs @@ -1,52 +1,51 @@ -namespace GraphQL.Client.Tests.Common.Helpers -{ - public class ConcurrentTaskWrapper - { - public static ConcurrentTaskWrapper New(Func> createTask) => new ConcurrentTaskWrapper(createTask); +namespace GraphQL.Client.Tests.Common.Helpers; - private readonly Func _createTask; - private Task _internalTask = null; - - public ConcurrentTaskWrapper(Func createTask) - { - _createTask = createTask; - } +public class ConcurrentTaskWrapper +{ + public static ConcurrentTaskWrapper New(Func> createTask) => new ConcurrentTaskWrapper(createTask); - public Task Invoke() - { - if (_internalTask != null) - return _internalTask; + private readonly Func _createTask; + private Task _internalTask = null; - return _internalTask = _createTask(); - } + public ConcurrentTaskWrapper(Func createTask) + { + _createTask = createTask; } - public class ConcurrentTaskWrapper + public Task Invoke() { - private readonly Func> _createTask; - private Task _internalTask = null; + if (_internalTask != null) + return _internalTask; - public ConcurrentTaskWrapper(Func> createTask) - { - _createTask = createTask; - } + return _internalTask = _createTask(); + } +} - public Task Invoke() - { - if (_internalTask != null) - return _internalTask; +public class ConcurrentTaskWrapper +{ + private readonly Func> _createTask; + private Task _internalTask = null; - return _internalTask = _createTask(); - } + public ConcurrentTaskWrapper(Func> createTask) + { + _createTask = createTask; + } - public void Start() - { - if (_internalTask == null) - _internalTask = _createTask(); - } + public Task Invoke() + { + if (_internalTask != null) + return _internalTask; - public Func> Invoking() => Invoke; + return _internalTask = _createTask(); + } - public void Clear() => _internalTask = null; + public void Start() + { + if (_internalTask == null) + _internalTask = _createTask(); } + + public Func> Invoking() => Invoke; + + public void Clear() => _internalTask = null; } diff --git a/tests/GraphQL.Client.Tests.Common/Helpers/MiscellaneousExtensions.cs b/tests/GraphQL.Client.Tests.Common/Helpers/MiscellaneousExtensions.cs index 428d4cca..d51afe07 100644 --- a/tests/GraphQL.Client.Tests.Common/Helpers/MiscellaneousExtensions.cs +++ b/tests/GraphQL.Client.Tests.Common/Helpers/MiscellaneousExtensions.cs @@ -1,24 +1,23 @@ using GraphQL.Client.Http; -namespace GraphQL.Client.Tests.Common.Helpers +namespace GraphQL.Client.Tests.Common.Helpers; + +public static class MiscellaneousExtensions { - public static class MiscellaneousExtensions - { - public static string RemoveWhitespace(this string input) => - new string(input.ToCharArray() - .Where(c => !char.IsWhiteSpace(c)) - .ToArray()); + public static string RemoveWhitespace(this string input) => + new string(input.ToCharArray() + .Where(c => !char.IsWhiteSpace(c)) + .ToArray()); - public static CallbackMonitor ConfigureMonitorForOnWebsocketConnected( - this GraphQLHttpClient client) + public static CallbackMonitor ConfigureMonitorForOnWebsocketConnected( + this GraphQLHttpClient client) + { + var tester = new CallbackMonitor(); + client.Options.OnWebsocketConnected = c => { - var tester = new CallbackMonitor(); - client.Options.OnWebsocketConnected = c => - { - tester.Invoke(c); - return Task.CompletedTask; - }; - return tester; - } + tester.Invoke(c); + return Task.CompletedTask; + }; + return tester; } } diff --git a/tests/GraphQL.Client.Tests.Common/Helpers/NetworkHelpers.cs b/tests/GraphQL.Client.Tests.Common/Helpers/NetworkHelpers.cs index c55eed75..dda8d6a0 100644 --- a/tests/GraphQL.Client.Tests.Common/Helpers/NetworkHelpers.cs +++ b/tests/GraphQL.Client.Tests.Common/Helpers/NetworkHelpers.cs @@ -1,17 +1,16 @@ using System.Net; using System.Net.Sockets; -namespace GraphQL.Client.Tests.Common.Helpers +namespace GraphQL.Client.Tests.Common.Helpers; + +public static class NetworkHelpers { - public static class NetworkHelpers + public static int GetFreeTcpPortNumber() { - public static int GetFreeTcpPortNumber() - { - var l = new TcpListener(IPAddress.Loopback, 0); - l.Start(); - var port = ((IPEndPoint)l.LocalEndpoint).Port; - l.Stop(); - return port; - } + var l = new TcpListener(IPAddress.Loopback, 0); + l.Start(); + var port = ((IPEndPoint)l.LocalEndpoint).Port; + l.Stop(); + return port; } } diff --git a/tests/GraphQL.Client.Tests.Common/StarWars/Extensions/ResolveFieldContextExtensions.cs b/tests/GraphQL.Client.Tests.Common/StarWars/Extensions/ResolveFieldContextExtensions.cs index ff3a12da..2aac17a6 100644 --- a/tests/GraphQL.Client.Tests.Common/StarWars/Extensions/ResolveFieldContextExtensions.cs +++ b/tests/GraphQL.Client.Tests.Common/StarWars/Extensions/ResolveFieldContextExtensions.cs @@ -2,61 +2,60 @@ using GraphQL.Client.Tests.Common.StarWars.Types; using GraphQL.Types.Relay.DataObjects; -namespace GraphQL.Client.Tests.Common.StarWars.Extensions +namespace GraphQL.Client.Tests.Common.StarWars.Extensions; + +public static class ResolveFieldContextExtensions { - public static class ResolveFieldContextExtensions + public static Connection GetPagedResults(this IResolveConnectionContext context, StarWarsData data, List ids) where U : StarWarsCharacter { - public static Connection GetPagedResults(this IResolveConnectionContext context, StarWarsData data, List ids) where U : StarWarsCharacter - { - List idList; - List list; - string cursor; - string endCursor; - var pageSize = context.PageSize ?? 20; + List idList; + List list; + string cursor; + string endCursor; + var pageSize = context.PageSize ?? 20; - if (context.IsUnidirectional || context.After != null || context.Before == null) + if (context.IsUnidirectional || context.After != null || context.Before == null) + { + if (context.After != null) + { + idList = ids + .SkipWhile(x => !Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(x)).Equals(context.After)) + .Take(context.First ?? pageSize).ToList(); + } + else + { + idList = ids + .Take(context.First ?? pageSize).ToList(); + } + } + else + { + if (context.Before != null) { - if (context.After != null) - { - idList = ids - .SkipWhile(x => !Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(x)).Equals(context.After)) - .Take(context.First ?? pageSize).ToList(); - } - else - { - idList = ids - .Take(context.First ?? pageSize).ToList(); - } + idList = ids.Reverse() + .SkipWhile(x => !Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(x)).Equals(context.Before)) + .Take(context.Last ?? pageSize).ToList(); } else { - if (context.Before != null) - { - idList = ids.Reverse() - .SkipWhile(x => !Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(x)).Equals(context.Before)) - .Take(context.Last ?? pageSize).ToList(); - } - else - { - idList = ids.Reverse() - .Take(context.Last ?? pageSize).ToList(); - } + idList = ids.Reverse() + .Take(context.Last ?? pageSize).ToList(); } + } - list = data.GetCharactersAsync(idList).Result as List ?? throw new InvalidOperationException($"GetCharactersAsync method should return list of '{typeof(U).Name}' items."); - cursor = list.Count > 0 ? list.Last().Cursor : null; - endCursor = ids.Count > 0 ? Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(ids.Last())) : null; + list = data.GetCharactersAsync(idList).Result as List ?? throw new InvalidOperationException($"GetCharactersAsync method should return list of '{typeof(U).Name}' items."); + cursor = list.Count > 0 ? list.Last().Cursor : null; + endCursor = ids.Count > 0 ? Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(ids.Last())) : null; - return new Connection + return new Connection + { + Edges = list.Select(x => new Edge { Cursor = x.Cursor, Node = x }).ToList(), + TotalCount = list.Count, + PageInfo = new PageInfo { - Edges = list.Select(x => new Edge { Cursor = x.Cursor, Node = x }).ToList(), - TotalCount = list.Count, - PageInfo = new PageInfo - { - EndCursor = endCursor, - HasNextPage = endCursor == null ? false : cursor != endCursor, - } - }; - } + EndCursor = endCursor, + HasNextPage = endCursor == null ? false : cursor != endCursor, + } + }; } } diff --git a/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsData.cs b/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsData.cs index c0d3595b..3c32d1f0 100644 --- a/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsData.cs +++ b/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsData.cs @@ -1,87 +1,86 @@ using GraphQL.Client.Tests.Common.StarWars.Types; -namespace GraphQL.Client.Tests.Common.StarWars +namespace GraphQL.Client.Tests.Common.StarWars; + +public class StarWarsData { - public class StarWarsData - { - private readonly List _characters = new List(); + private readonly List _characters = new List(); - public StarWarsData() + public StarWarsData() + { + _characters.Add(new Human { - _characters.Add(new Human - { - Id = "1", - Name = "Luke", - Friends = new List { "3", "4" }, - AppearsIn = new[] { 4, 5, 6 }, - HomePlanet = "Tatooine", - Cursor = "MQ==" - }); - _characters.Add(new Human - { - Id = "2", - Name = "Vader", - AppearsIn = new[] { 4, 5, 6 }, - HomePlanet = "Tatooine", - Cursor = "Mg==" - }); - - _characters.Add(new Droid - { - Id = "3", - Name = "R2-D2", - Friends = new List { "1", "4" }, - AppearsIn = new[] { 4, 5, 6 }, - PrimaryFunction = "Astromech", - Cursor = "Mw==" - }); - _characters.Add(new Droid - { - Id = "4", - Name = "C-3PO", - AppearsIn = new[] { 4, 5, 6 }, - PrimaryFunction = "Protocol", - Cursor = "NA==" - }); - } - - public IEnumerable GetFriends(StarWarsCharacter character) + Id = "1", + Name = "Luke", + Friends = new List { "3", "4" }, + AppearsIn = new[] { 4, 5, 6 }, + HomePlanet = "Tatooine", + Cursor = "MQ==" + }); + _characters.Add(new Human { - if (character == null) - { - return null; - } + Id = "2", + Name = "Vader", + AppearsIn = new[] { 4, 5, 6 }, + HomePlanet = "Tatooine", + Cursor = "Mg==" + }); - var friends = new List(); - var lookup = character.Friends; - if (lookup != null) - { - foreach (var c in _characters.Where(h => lookup.Contains(h.Id))) - friends.Add(c); - } - return friends; - } - - public StarWarsCharacter AddCharacter(StarWarsCharacter character) + _characters.Add(new Droid { - character.Id = _characters.Count.ToString(); - _characters.Add(character); - return character; - } - - public Task GetHumanByIdAsync(string id) + Id = "3", + Name = "R2-D2", + Friends = new List { "1", "4" }, + AppearsIn = new[] { 4, 5, 6 }, + PrimaryFunction = "Astromech", + Cursor = "Mw==" + }); + _characters.Add(new Droid { - return Task.FromResult(_characters.FirstOrDefault(h => h.Id == id && h is Human) as Human); - } + Id = "4", + Name = "C-3PO", + AppearsIn = new[] { 4, 5, 6 }, + PrimaryFunction = "Protocol", + Cursor = "NA==" + }); + } - public Task GetDroidByIdAsync(string id) + public IEnumerable GetFriends(StarWarsCharacter character) + { + if (character == null) { - return Task.FromResult(_characters.FirstOrDefault(h => h.Id == id && h is Droid) as Droid); + return null; } - public Task> GetCharactersAsync(List guids) + var friends = new List(); + var lookup = character.Friends; + if (lookup != null) { - return Task.FromResult(_characters.Where(c => guids.Contains(c.Id)).ToList()); + foreach (var c in _characters.Where(h => lookup.Contains(h.Id))) + friends.Add(c); } + return friends; + } + + public StarWarsCharacter AddCharacter(StarWarsCharacter character) + { + character.Id = _characters.Count.ToString(); + _characters.Add(character); + return character; + } + + public Task GetHumanByIdAsync(string id) + { + return Task.FromResult(_characters.FirstOrDefault(h => h.Id == id && h is Human) as Human); + } + + public Task GetDroidByIdAsync(string id) + { + return Task.FromResult(_characters.FirstOrDefault(h => h.Id == id && h is Droid) as Droid); + } + + public Task> GetCharactersAsync(List guids) + { + return Task.FromResult(_characters.Where(c => guids.Contains(c.Id)).ToList()); } } diff --git a/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsMutation.cs b/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsMutation.cs index 130ee1a9..161b25dd 100644 --- a/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsMutation.cs +++ b/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsMutation.cs @@ -1,36 +1,35 @@ using GraphQL.Client.Tests.Common.StarWars.Types; using GraphQL.Types; -namespace GraphQL.Client.Tests.Common.StarWars +namespace GraphQL.Client.Tests.Common.StarWars; + +/// Mutation graph type for StarWars schema. +/// +/// This is an example JSON request for a mutation +/// { +/// "query": "mutation ($human:HumanInput!){ createHuman(human: $human) { id name } }", +/// "variables": { +/// "human": { +/// "name": "Boba Fett" +/// } +/// } +/// } +/// +public class StarWarsMutation : ObjectGraphType { - /// Mutation graph type for StarWars schema. - /// - /// This is an example JSON request for a mutation - /// { - /// "query": "mutation ($human:HumanInput!){ createHuman(human: $human) { id name } }", - /// "variables": { - /// "human": { - /// "name": "Boba Fett" - /// } - /// } - /// } - /// - public class StarWarsMutation : ObjectGraphType + public StarWarsMutation(StarWarsData data) { - public StarWarsMutation(StarWarsData data) - { - Name = "Mutation"; + Name = "Mutation"; - Field( - "createHuman", - arguments: new QueryArguments( - new QueryArgument> { Name = "human" } - ), - resolve: context => - { - var human = context.GetArgument("human"); - return data.AddCharacter(human); - }); - } + Field( + "createHuman", + arguments: new QueryArguments( + new QueryArgument> { Name = "human" } + ), + resolve: context => + { + var human = context.GetArgument("human"); + return data.AddCharacter(human); + }); } } diff --git a/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsQuery.cs b/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsQuery.cs index 38b29e6f..a3fb05e9 100644 --- a/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsQuery.cs +++ b/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsQuery.cs @@ -1,32 +1,31 @@ using GraphQL.Client.Tests.Common.StarWars.Types; using GraphQL.Types; -namespace GraphQL.Client.Tests.Common.StarWars +namespace GraphQL.Client.Tests.Common.StarWars; + +public class StarWarsQuery : ObjectGraphType { - public class StarWarsQuery : ObjectGraphType + public StarWarsQuery(StarWarsData data) { - public StarWarsQuery(StarWarsData data) - { - Name = "Query"; + Name = "Query"; - FieldAsync("hero", resolve: async context => await data.GetDroidByIdAsync("3")); - FieldAsync( - "human", - arguments: new QueryArguments( - new QueryArgument> { Name = "id", Description = "id of the human" } - ), - resolve: async context => await data.GetHumanByIdAsync(context.GetArgument("id")) - ); + FieldAsync("hero", resolve: async context => await data.GetDroidByIdAsync("3")); + FieldAsync( + "human", + arguments: new QueryArguments( + new QueryArgument> { Name = "id", Description = "id of the human" } + ), + resolve: async context => await data.GetHumanByIdAsync(context.GetArgument("id")) + ); - Func> func = (context, id) => data.GetDroidByIdAsync(id); + Func> func = (context, id) => data.GetDroidByIdAsync(id); - FieldDelegate( - "droid", - arguments: new QueryArguments( - new QueryArgument> { Name = "id", Description = "id of the droid" } - ), - resolve: func - ); - } + FieldDelegate( + "droid", + arguments: new QueryArguments( + new QueryArgument> { Name = "id", Description = "id of the droid" } + ), + resolve: func + ); } } diff --git a/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsSchema.cs b/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsSchema.cs index 3756f471..e2220220 100644 --- a/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsSchema.cs +++ b/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsSchema.cs @@ -1,17 +1,16 @@ using GraphQL.Types; using Microsoft.Extensions.DependencyInjection; -namespace GraphQL.Client.Tests.Common.StarWars +namespace GraphQL.Client.Tests.Common.StarWars; + +public class StarWarsSchema : Schema { - public class StarWarsSchema : Schema + public StarWarsSchema(IServiceProvider serviceProvider) + : base(serviceProvider) { - public StarWarsSchema(IServiceProvider serviceProvider) - : base(serviceProvider) - { - Query = serviceProvider.GetRequiredService(); - Mutation = serviceProvider.GetRequiredService(); + Query = serviceProvider.GetRequiredService(); + Mutation = serviceProvider.GetRequiredService(); - Description = "Example StarWars universe schema"; - } + Description = "Example StarWars universe schema"; } } diff --git a/tests/GraphQL.Client.Tests.Common/StarWars/TestData/StarWarsHumans.cs b/tests/GraphQL.Client.Tests.Common/StarWars/TestData/StarWarsHumans.cs index 25376b87..4efd812e 100644 --- a/tests/GraphQL.Client.Tests.Common/StarWars/TestData/StarWarsHumans.cs +++ b/tests/GraphQL.Client.Tests.Common/StarWars/TestData/StarWarsHumans.cs @@ -1,18 +1,17 @@ using System.Collections; -namespace GraphQL.Client.Tests.Common.StarWars.TestData +namespace GraphQL.Client.Tests.Common.StarWars.TestData; + +/// +/// Test data object +/// +public class StarWarsHumans : IEnumerable { - /// - /// Test data object - /// - public class StarWarsHumans : IEnumerable + public IEnumerator GetEnumerator() { - public IEnumerator GetEnumerator() - { - yield return new object[] { 1, "Luke" }; - yield return new object[] { 2, "Vader" }; - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + yield return new object[] { 1, "Luke" }; + yield return new object[] { 2, "Vader" }; } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } diff --git a/tests/GraphQL.Client.Tests.Common/StarWars/Types/CharacterInterface.cs b/tests/GraphQL.Client.Tests.Common/StarWars/Types/CharacterInterface.cs index e45db3d4..1e3809dc 100644 --- a/tests/GraphQL.Client.Tests.Common/StarWars/Types/CharacterInterface.cs +++ b/tests/GraphQL.Client.Tests.Common/StarWars/Types/CharacterInterface.cs @@ -1,20 +1,19 @@ using GraphQL.Types; using GraphQL.Types.Relay; -namespace GraphQL.Client.Tests.Common.StarWars.Types +namespace GraphQL.Client.Tests.Common.StarWars.Types; + +public class CharacterInterface : InterfaceGraphType { - public class CharacterInterface : InterfaceGraphType + public CharacterInterface() { - public CharacterInterface() - { - Name = "Character"; + Name = "Character"; - Field>("id", "The id of the character.", resolve: context => context.Source.Id); - Field("name", "The name of the character.", resolve: context => context.Source.Name); + Field>("id", "The id of the character.", resolve: context => context.Source.Id); + Field("name", "The name of the character.", resolve: context => context.Source.Name); - Field>("friends"); - Field>>("friendsConnection"); - Field>("appearsIn", "Which movie they appear in."); - } + Field>("friends"); + Field>>("friendsConnection"); + Field>("appearsIn", "Which movie they appear in."); } } diff --git a/tests/GraphQL.Client.Tests.Common/StarWars/Types/DroidType.cs b/tests/GraphQL.Client.Tests.Common/StarWars/Types/DroidType.cs index 8d8fc867..cc9e33c3 100644 --- a/tests/GraphQL.Client.Tests.Common/StarWars/Types/DroidType.cs +++ b/tests/GraphQL.Client.Tests.Common/StarWars/Types/DroidType.cs @@ -1,30 +1,29 @@ using GraphQL.Client.Tests.Common.StarWars.Extensions; using GraphQL.Types; -namespace GraphQL.Client.Tests.Common.StarWars.Types +namespace GraphQL.Client.Tests.Common.StarWars.Types; + +public class DroidType : ObjectGraphType { - public class DroidType : ObjectGraphType + public DroidType(StarWarsData data) { - public DroidType(StarWarsData data) - { - Name = "Droid"; - Description = "A mechanical creature in the Star Wars universe."; + Name = "Droid"; + Description = "A mechanical creature in the Star Wars universe."; - Field>("id", "The id of the droid.", resolve: context => context.Source.Id); - Field("name", "The name of the droid.", resolve: context => context.Source.Name); + Field>("id", "The id of the droid.", resolve: context => context.Source.Id); + Field("name", "The name of the droid.", resolve: context => context.Source.Name); - Field>("friends", resolve: context => data.GetFriends(context.Source)); + Field>("friends", resolve: context => data.GetFriends(context.Source)); - Connection() - .Name("friendsConnection") - .Description("A list of a character's friends.") - .Bidirectional() - .Resolve(context => context.GetPagedResults(data, context.Source.Friends)); + Connection() + .Name("friendsConnection") + .Description("A list of a character's friends.") + .Bidirectional() + .Resolve(context => context.GetPagedResults(data, context.Source.Friends)); - Field>("appearsIn", "Which movie they appear in."); - Field("primaryFunction", "The primary function of the droid.", resolve: context => context.Source.PrimaryFunction); + Field>("appearsIn", "Which movie they appear in."); + Field("primaryFunction", "The primary function of the droid.", resolve: context => context.Source.PrimaryFunction); - Interface(); - } + Interface(); } } diff --git a/tests/GraphQL.Client.Tests.Common/StarWars/Types/EpisodeEnum.cs b/tests/GraphQL.Client.Tests.Common/StarWars/Types/EpisodeEnum.cs index 4b9fb577..7fbc855f 100644 --- a/tests/GraphQL.Client.Tests.Common/StarWars/Types/EpisodeEnum.cs +++ b/tests/GraphQL.Client.Tests.Common/StarWars/Types/EpisodeEnum.cs @@ -1,23 +1,22 @@ using GraphQL.Types; -namespace GraphQL.Client.Tests.Common.StarWars.Types +namespace GraphQL.Client.Tests.Common.StarWars.Types; + +public class EpisodeEnum : EnumerationGraphType { - public class EpisodeEnum : EnumerationGraphType + public EpisodeEnum() { - public EpisodeEnum() - { - Name = "Episode"; - Description = "One of the films in the Star Wars Trilogy."; - Add("NEWHOPE", 4, "Released in 1977."); - Add("EMPIRE", 5, "Released in 1980."); - Add("JEDI", 6, "Released in 1983."); - } + Name = "Episode"; + Description = "One of the films in the Star Wars Trilogy."; + Add("NEWHOPE", 4, "Released in 1977."); + Add("EMPIRE", 5, "Released in 1980."); + Add("JEDI", 6, "Released in 1983."); } +} - public enum Episodes - { - NEWHOPE = 4, - EMPIRE = 5, - JEDI = 6 - } +public enum Episodes +{ + NEWHOPE = 4, + EMPIRE = 5, + JEDI = 6 } diff --git a/tests/GraphQL.Client.Tests.Common/StarWars/Types/HumanInputType.cs b/tests/GraphQL.Client.Tests.Common/StarWars/Types/HumanInputType.cs index c44c381c..0402876e 100644 --- a/tests/GraphQL.Client.Tests.Common/StarWars/Types/HumanInputType.cs +++ b/tests/GraphQL.Client.Tests.Common/StarWars/Types/HumanInputType.cs @@ -1,14 +1,13 @@ using GraphQL.Types; -namespace GraphQL.Client.Tests.Common.StarWars.Types +namespace GraphQL.Client.Tests.Common.StarWars.Types; + +public class HumanInputType : InputObjectGraphType { - public class HumanInputType : InputObjectGraphType + public HumanInputType() { - public HumanInputType() - { - Name = "HumanInput"; - Field>("name"); - Field("homePlanet"); - } + Name = "HumanInput"; + Field>("name"); + Field("homePlanet"); } } diff --git a/tests/GraphQL.Client.Tests.Common/StarWars/Types/HumanType.cs b/tests/GraphQL.Client.Tests.Common/StarWars/Types/HumanType.cs index 93da8165..552aabdd 100644 --- a/tests/GraphQL.Client.Tests.Common/StarWars/Types/HumanType.cs +++ b/tests/GraphQL.Client.Tests.Common/StarWars/Types/HumanType.cs @@ -1,30 +1,29 @@ using GraphQL.Client.Tests.Common.StarWars.Extensions; using GraphQL.Types; -namespace GraphQL.Client.Tests.Common.StarWars.Types +namespace GraphQL.Client.Tests.Common.StarWars.Types; + +public class HumanType : ObjectGraphType { - public class HumanType : ObjectGraphType + public HumanType(StarWarsData data) { - public HumanType(StarWarsData data) - { - Name = "Human"; + Name = "Human"; - Field>("id", "The id of the human.", resolve: context => context.Source.Id); - Field("name", "The name of the human.", resolve: context => context.Source.Name); + Field>("id", "The id of the human.", resolve: context => context.Source.Id); + Field("name", "The name of the human.", resolve: context => context.Source.Name); - Field>("friends", resolve: context => data.GetFriends(context.Source)); + Field>("friends", resolve: context => data.GetFriends(context.Source)); - Connection() - .Name("friendsConnection") - .Description("A list of a character's friends.") - .Bidirectional() - .Resolve(context => context.GetPagedResults(data, context.Source.Friends)); + Connection() + .Name("friendsConnection") + .Description("A list of a character's friends.") + .Bidirectional() + .Resolve(context => context.GetPagedResults(data, context.Source.Friends)); - Field>("appearsIn", "Which movie they appear in."); + Field>("appearsIn", "Which movie they appear in."); - Field("homePlanet", "The home planet of the human.", resolve: context => context.Source.HomePlanet); + Field("homePlanet", "The home planet of the human.", resolve: context => context.Source.HomePlanet); - Interface(); - } + Interface(); } } diff --git a/tests/GraphQL.Client.Tests.Common/StarWars/Types/StarWarsCharacter.cs b/tests/GraphQL.Client.Tests.Common/StarWars/Types/StarWarsCharacter.cs index 6c646e0b..fb2688f3 100644 --- a/tests/GraphQL.Client.Tests.Common/StarWars/Types/StarWarsCharacter.cs +++ b/tests/GraphQL.Client.Tests.Common/StarWars/Types/StarWarsCharacter.cs @@ -1,21 +1,20 @@ -namespace GraphQL.Client.Tests.Common.StarWars.Types +namespace GraphQL.Client.Tests.Common.StarWars.Types; + +public abstract class StarWarsCharacter { - public abstract class StarWarsCharacter - { - public string Id { get; set; } - public string Name { get; set; } - public List Friends { get; set; } - public int[] AppearsIn { get; set; } - public string Cursor { get; set; } - } + public string Id { get; set; } + public string Name { get; set; } + public List Friends { get; set; } + public int[] AppearsIn { get; set; } + public string Cursor { get; set; } +} - public class Human : StarWarsCharacter - { - public string HomePlanet { get; set; } - } +public class Human : StarWarsCharacter +{ + public string HomePlanet { get; set; } +} - public class Droid : StarWarsCharacter - { - public string PrimaryFunction { get; set; } - } +public class Droid : StarWarsCharacter +{ + public string PrimaryFunction { get; set; } } diff --git a/tests/GraphQL.Integration.Tests/Helpers/IntegrationServerTestFixture.cs b/tests/GraphQL.Integration.Tests/Helpers/IntegrationServerTestFixture.cs index 59dc0733..b0154c2a 100644 --- a/tests/GraphQL.Integration.Tests/Helpers/IntegrationServerTestFixture.cs +++ b/tests/GraphQL.Integration.Tests/Helpers/IntegrationServerTestFixture.cs @@ -5,59 +5,58 @@ using GraphQL.Client.Tests.Common; using GraphQL.Client.Tests.Common.Helpers; -namespace GraphQL.Integration.Tests.Helpers +namespace GraphQL.Integration.Tests.Helpers; + +public abstract class IntegrationServerTestFixture { - public abstract class IntegrationServerTestFixture + public int Port { get; private set; } + + public IWebHost Server { get; private set; } + + public abstract IGraphQLWebsocketJsonSerializer Serializer { get; } + + public IntegrationServerTestFixture() { - public int Port { get; private set; } - - public IWebHost Server { get; private set; } - - public abstract IGraphQLWebsocketJsonSerializer Serializer { get; } - - public IntegrationServerTestFixture() - { - Port = NetworkHelpers.GetFreeTcpPortNumber(); - } - - public async Task CreateServer() - { - if (Server != null) - return; - Server = await WebHostHelpers.CreateServer(Port); - } - - public async Task ShutdownServer() - { - if (Server == null) - return; - - await Server.StopAsync(); - Server.Dispose(); - Server = null; - } - - public GraphQLHttpClient GetStarWarsClient(bool requestsViaWebsocket = false) - => GetGraphQLClient(Common.STAR_WARS_ENDPOINT, requestsViaWebsocket); - - public GraphQLHttpClient GetChatClient(bool requestsViaWebsocket = false) - => GetGraphQLClient(Common.CHAT_ENDPOINT, requestsViaWebsocket); - - private GraphQLHttpClient GetGraphQLClient(string endpoint, bool requestsViaWebsocket = false) - { - if (Serializer == null) - throw new InvalidOperationException("JSON serializer not configured"); - return WebHostHelpers.GetGraphQLClient(Port, endpoint, requestsViaWebsocket, Serializer); - } + Port = NetworkHelpers.GetFreeTcpPortNumber(); } - public class NewtonsoftIntegrationServerTestFixture : IntegrationServerTestFixture + public async Task CreateServer() { - public override IGraphQLWebsocketJsonSerializer Serializer { get; } = new NewtonsoftJsonSerializer(); + if (Server != null) + return; + Server = await WebHostHelpers.CreateServer(Port); + } + + public async Task ShutdownServer() + { + if (Server == null) + return; + + await Server.StopAsync(); + Server.Dispose(); + Server = null; } - public class SystemTextJsonIntegrationServerTestFixture : IntegrationServerTestFixture + public GraphQLHttpClient GetStarWarsClient(bool requestsViaWebsocket = false) + => GetGraphQLClient(Common.STAR_WARS_ENDPOINT, requestsViaWebsocket); + + public GraphQLHttpClient GetChatClient(bool requestsViaWebsocket = false) + => GetGraphQLClient(Common.CHAT_ENDPOINT, requestsViaWebsocket); + + private GraphQLHttpClient GetGraphQLClient(string endpoint, bool requestsViaWebsocket = false) { - public override IGraphQLWebsocketJsonSerializer Serializer { get; } = new SystemTextJsonSerializer(); + if (Serializer == null) + throw new InvalidOperationException("JSON serializer not configured"); + return WebHostHelpers.GetGraphQLClient(Port, endpoint, requestsViaWebsocket, Serializer); } } + +public class NewtonsoftIntegrationServerTestFixture : IntegrationServerTestFixture +{ + public override IGraphQLWebsocketJsonSerializer Serializer { get; } = new NewtonsoftJsonSerializer(); +} + +public class SystemTextJsonIntegrationServerTestFixture : IntegrationServerTestFixture +{ + public override IGraphQLWebsocketJsonSerializer Serializer { get; } = new SystemTextJsonSerializer(); +} diff --git a/tests/GraphQL.Integration.Tests/Helpers/WebHostHelpers.cs b/tests/GraphQL.Integration.Tests/Helpers/WebHostHelpers.cs index fb3ba46b..ac90c08b 100644 --- a/tests/GraphQL.Integration.Tests/Helpers/WebHostHelpers.cs +++ b/tests/GraphQL.Integration.Tests/Helpers/WebHostHelpers.cs @@ -5,62 +5,61 @@ using GraphQL.Client.Tests.Common.Helpers; using IntegrationTestServer; -namespace GraphQL.Integration.Tests.Helpers +namespace GraphQL.Integration.Tests.Helpers; + +public static class WebHostHelpers { - public static class WebHostHelpers + public static async Task CreateServer(int port) { - public static async Task CreateServer(int port) - { - var configBuilder = new ConfigurationBuilder(); - configBuilder.AddInMemoryCollection(); - var config = configBuilder.Build(); - config["server.urls"] = $"http://localhost:{port}"; - - var host = new WebHostBuilder() - .ConfigureLogging((ctx, logging) => logging.AddDebug()) - .UseConfiguration(config) - .UseKestrel() - .UseStartup() - .Build(); + var configBuilder = new ConfigurationBuilder(); + configBuilder.AddInMemoryCollection(); + var config = configBuilder.Build(); + config["server.urls"] = $"http://localhost:{port}"; - var tcs = new TaskCompletionSource(); - host.Services.GetService().ApplicationStarted.Register(() => tcs.TrySetResult(true)); - await host.StartAsync(); - await tcs.Task; - return host; - } + var host = new WebHostBuilder() + .ConfigureLogging((ctx, logging) => logging.AddDebug()) + .UseConfiguration(config) + .UseKestrel() + .UseStartup() + .Build(); - public static GraphQLHttpClient GetGraphQLClient(int port, string endpoint, bool requestsViaWebsocket = false, IGraphQLWebsocketJsonSerializer serializer = null) - => new GraphQLHttpClient(new GraphQLHttpClientOptions - { - EndPoint = new Uri($"http://localhost:{port}{endpoint}"), - UseWebSocketForQueriesAndMutations = requestsViaWebsocket - }, - serializer ?? new NewtonsoftJsonSerializer()); + var tcs = new TaskCompletionSource(); + host.Services.GetService().ApplicationStarted.Register(() => tcs.TrySetResult(true)); + await host.StartAsync(); + await tcs.Task; + return host; } - public class TestServerSetup : IDisposable - { - public TestServerSetup(IGraphQLWebsocketJsonSerializer serializer) + public static GraphQLHttpClient GetGraphQLClient(int port, string endpoint, bool requestsViaWebsocket = false, IGraphQLWebsocketJsonSerializer serializer = null) + => new GraphQLHttpClient(new GraphQLHttpClientOptions { - Serializer = serializer; - Port = NetworkHelpers.GetFreeTcpPortNumber(); - } + EndPoint = new Uri($"http://localhost:{port}{endpoint}"), + UseWebSocketForQueriesAndMutations = requestsViaWebsocket + }, + serializer ?? new NewtonsoftJsonSerializer()); +} - public int Port { get; } +public class TestServerSetup : IDisposable +{ + public TestServerSetup(IGraphQLWebsocketJsonSerializer serializer) + { + Serializer = serializer; + Port = NetworkHelpers.GetFreeTcpPortNumber(); + } - public IWebHost Server { get; set; } + public int Port { get; } - public IGraphQLWebsocketJsonSerializer Serializer { get; set; } + public IWebHost Server { get; set; } - public GraphQLHttpClient GetStarWarsClient(bool requestsViaWebsocket = false) - => GetGraphQLClient(Common.STAR_WARS_ENDPOINT, requestsViaWebsocket); + public IGraphQLWebsocketJsonSerializer Serializer { get; set; } - public GraphQLHttpClient GetChatClient(bool requestsViaWebsocket = false) - => GetGraphQLClient(Common.CHAT_ENDPOINT, requestsViaWebsocket); + public GraphQLHttpClient GetStarWarsClient(bool requestsViaWebsocket = false) + => GetGraphQLClient(Common.STAR_WARS_ENDPOINT, requestsViaWebsocket); - private GraphQLHttpClient GetGraphQLClient(string endpoint, bool requestsViaWebsocket = false) => WebHostHelpers.GetGraphQLClient(Port, endpoint, requestsViaWebsocket); + public GraphQLHttpClient GetChatClient(bool requestsViaWebsocket = false) + => GetGraphQLClient(Common.CHAT_ENDPOINT, requestsViaWebsocket); - public void Dispose() => Server?.Dispose(); - } + private GraphQLHttpClient GetGraphQLClient(string endpoint, bool requestsViaWebsocket = false) => WebHostHelpers.GetGraphQLClient(Port, endpoint, requestsViaWebsocket); + + public void Dispose() => Server?.Dispose(); } diff --git a/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs b/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs index e489046a..600ca463 100644 --- a/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs +++ b/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs @@ -8,99 +8,99 @@ using GraphQL.Integration.Tests.Helpers; using Xunit; -namespace GraphQL.Integration.Tests.QueryAndMutationTests +namespace GraphQL.Integration.Tests.QueryAndMutationTests; + +public abstract class Base : IAsyncLifetime { - public abstract class Base : IAsyncLifetime + protected IntegrationServerTestFixture Fixture; + protected GraphQLHttpClient StarWarsClient; + protected GraphQLHttpClient ChatClient; + + protected Base(IntegrationServerTestFixture fixture) { - protected IntegrationServerTestFixture Fixture; - protected GraphQLHttpClient StarWarsClient; - protected GraphQLHttpClient ChatClient; + Fixture = fixture; + } - protected Base(IntegrationServerTestFixture fixture) - { - Fixture = fixture; - } + public async Task InitializeAsync() + { + await Fixture.CreateServer(); + StarWarsClient = Fixture.GetStarWarsClient(); + ChatClient = Fixture.GetChatClient(); + } - public async Task InitializeAsync() - { - await Fixture.CreateServer(); - StarWarsClient = Fixture.GetStarWarsClient(); - ChatClient = Fixture.GetChatClient(); - } + public Task DisposeAsync() + { + ChatClient?.Dispose(); + StarWarsClient?.Dispose(); + return Task.CompletedTask; + } - public Task DisposeAsync() - { - ChatClient?.Dispose(); - StarWarsClient?.Dispose(); - return Task.CompletedTask; - } - - [Theory] - [ClassData(typeof(StarWarsHumans))] - public async void QueryTheory(int id, string name) - { - var graphQLRequest = new GraphQLRequest($"{{ human(id: \"{id}\") {{ name }} }}"); - var response = await StarWarsClient.SendQueryAsync(graphQLRequest, () => new { Human = new { Name = string.Empty } }); + [Theory] + [ClassData(typeof(StarWarsHumans))] + public async void QueryTheory(int id, string name) + { + var graphQLRequest = new GraphQLRequest($"{{ human(id: \"{id}\") {{ name }} }}"); + var response = await StarWarsClient.SendQueryAsync(graphQLRequest, () => new { Human = new { Name = string.Empty } }); - Assert.Null(response.Errors); - Assert.Equal(name, response.Data.Human.Name); - } + Assert.Null(response.Errors); + Assert.Equal(name, response.Data.Human.Name); + } - [Theory] - [ClassData(typeof(StarWarsHumans))] - public async void QueryAsHttpResponseTheory(int id, string name) - { - var graphQLRequest = new GraphQLRequest($"{{ human(id: \"{id}\") {{ name }} }}"); - var responseType = new { Human = new { Name = string.Empty } }; - var response = await StarWarsClient.SendQueryAsync(graphQLRequest, () => responseType); + [Theory] + [ClassData(typeof(StarWarsHumans))] + public async void QueryAsHttpResponseTheory(int id, string name) + { + var graphQLRequest = new GraphQLRequest($"{{ human(id: \"{id}\") {{ name }} }}"); + var responseType = new { Human = new { Name = string.Empty } }; + var response = await StarWarsClient.SendQueryAsync(graphQLRequest, () => responseType); - FluentActions.Invoking(() => response.AsGraphQLHttpResponse()).Should() - .NotThrow("because the returned object is a GraphQLHttpResponse"); + FluentActions.Invoking(() => response.AsGraphQLHttpResponse()).Should() + .NotThrow("because the returned object is a GraphQLHttpResponse"); - var httpResponse = response.AsGraphQLHttpResponse(); + var httpResponse = response.AsGraphQLHttpResponse(); - httpResponse.Errors.Should().BeNull(); - httpResponse.Data.Human.Name.Should().Be(name); + httpResponse.Errors.Should().BeNull(); + httpResponse.Data.Human.Name.Should().Be(name); - httpResponse.StatusCode.Should().Be(HttpStatusCode.OK); - httpResponse.ResponseHeaders.Date.Should().BeCloseTo(DateTimeOffset.Now, TimeSpan.FromMinutes(1)); - } + httpResponse.StatusCode.Should().Be(HttpStatusCode.OK); + httpResponse.ResponseHeaders.Date.Should().BeCloseTo(DateTimeOffset.Now, TimeSpan.FromMinutes(1)); + } - [Theory(Skip = "System.Json.Net deserializes 'dynamic' as JsonElement.")] - [ClassData(typeof(StarWarsHumans))] - public async void QueryWithDynamicReturnTypeTheory(int id, string name) - { - var graphQLRequest = new GraphQLRequest($"{{ human(id: \"{id}\") {{ name }} }}"); + [Theory(Skip = "System.Json.Net deserializes 'dynamic' as JsonElement.")] + [ClassData(typeof(StarWarsHumans))] + public async void QueryWithDynamicReturnTypeTheory(int id, string name) + { + var graphQLRequest = new GraphQLRequest($"{{ human(id: \"{id}\") {{ name }} }}"); - var response = await StarWarsClient.SendQueryAsync(graphQLRequest); + var response = await StarWarsClient.SendQueryAsync(graphQLRequest); - Assert.Null(response.Errors); - Assert.Equal(name, response.Data.human.name.ToString()); - } + Assert.Null(response.Errors); + Assert.Equal(name, response.Data.human.name.ToString()); + } - [Theory] - [ClassData(typeof(StarWarsHumans))] - public async void QueryWitVarsTheory(int id, string name) - { - var graphQLRequest = new GraphQLRequest(@" + [Theory] + [ClassData(typeof(StarWarsHumans))] + public async void QueryWitVarsTheory(int id, string name) + { + var graphQLRequest = new GraphQLRequest(@" query Human($id: String!){ human(id: $id) { name } }", - new { id = id.ToString() }); + new { id = id.ToString() }); - var response = await StarWarsClient.SendQueryAsync(graphQLRequest, () => new { Human = new { Name = string.Empty } }); + var response = await StarWarsClient.SendQueryAsync(graphQLRequest, () => new { Human = new { Name = string.Empty } }); - Assert.Null(response.Errors); - Assert.Equal(name, response.Data.Human.Name); - } + Assert.Null(response.Errors); + Assert.Equal(name, response.Data.Human.Name); + } - [Theory] - [ClassData(typeof(StarWarsHumans))] - public async void QueryWitVarsAndOperationNameTheory(int id, string name) - { - var graphQLRequest = new GraphQLRequest(@" + [Theory] + [ClassData(typeof(StarWarsHumans))] + public async void QueryWitVarsAndOperationNameTheory(int id, string name) + { + var graphQLRequest = new GraphQLRequest(@" query Human($id: String!){ human(id: $id) { name @@ -112,19 +112,19 @@ query Droid($id: String!) { name } }", - new { id = id.ToString() }, - "Human"); + new { id = id.ToString() }, + "Human"); - var response = await StarWarsClient.SendQueryAsync(graphQLRequest, () => new { Human = new { Name = string.Empty } }); + var response = await StarWarsClient.SendQueryAsync(graphQLRequest, () => new { Human = new { Name = string.Empty } }); - Assert.Null(response.Errors); - Assert.Equal(name, response.Data.Human.Name); - } + Assert.Null(response.Errors); + Assert.Equal(name, response.Data.Human.Name); + } - [Fact] - public async void SendMutationFact() - { - var mutationRequest = new GraphQLRequest(@" + [Fact] + public async void SendMutationFact() + { + var mutationRequest = new GraphQLRequest(@" mutation CreateHuman($human: HumanInput!) { createHuman(human: $human) { id @@ -132,90 +132,89 @@ mutation CreateHuman($human: HumanInput!) { homePlanet } }", - new { human = new { name = "Han Solo", homePlanet = "Corellia" } }); + new { human = new { name = "Han Solo", homePlanet = "Corellia" } }); - var queryRequest = new GraphQLRequest(@" + var queryRequest = new GraphQLRequest(@" query Human($id: String!){ human(id: $id) { name } }"); - var mutationResponse = await StarWarsClient.SendMutationAsync(mutationRequest, () => new - { - createHuman = new - { - Id = "", - Name = "", - HomePlanet = "" - } - }); - - Assert.Null(mutationResponse.Errors); - Assert.Equal("Han Solo", mutationResponse.Data.createHuman.Name); - Assert.Equal("Corellia", mutationResponse.Data.createHuman.HomePlanet); - - queryRequest.Variables = new { id = mutationResponse.Data.createHuman.Id }; - var queryResponse = await StarWarsClient.SendQueryAsync(queryRequest, () => new { Human = new { Name = string.Empty } }); - - Assert.Null(queryResponse.Errors); - Assert.Equal("Han Solo", queryResponse.Data.Human.Name); - } - - [Fact] - public async void PreprocessHttpRequestMessageIsCalled() + var mutationResponse = await StarWarsClient.SendMutationAsync(mutationRequest, () => new { - var callbackTester = new CallbackMonitor(); - var graphQLRequest = new GraphQLHttpRequest($"{{ human(id: \"1\") {{ name }} }}") + createHuman = new { + Id = "", + Name = "", + HomePlanet = "" + } + }); + + Assert.Null(mutationResponse.Errors); + Assert.Equal("Han Solo", mutationResponse.Data.createHuman.Name); + Assert.Equal("Corellia", mutationResponse.Data.createHuman.HomePlanet); + + queryRequest.Variables = new { id = mutationResponse.Data.createHuman.Id }; + var queryResponse = await StarWarsClient.SendQueryAsync(queryRequest, () => new { Human = new { Name = string.Empty } }); + + Assert.Null(queryResponse.Errors); + Assert.Equal("Han Solo", queryResponse.Data.Human.Name); + } + + [Fact] + public async void PreprocessHttpRequestMessageIsCalled() + { + var callbackTester = new CallbackMonitor(); + var graphQLRequest = new GraphQLHttpRequest($"{{ human(id: \"1\") {{ name }} }}") + { #pragma warning disable CS0618 // Type or member is obsolete - PreprocessHttpRequestMessage = callbackTester.Invoke + PreprocessHttpRequestMessage = callbackTester.Invoke #pragma warning restore CS0618 // Type or member is obsolete - }; + }; - var defaultHeaders = StarWarsClient.HttpClient.DefaultRequestHeaders; - var response = await StarWarsClient.SendQueryAsync(graphQLRequest, () => new { Human = new { Name = string.Empty } }); - callbackTester.Should().HaveBeenInvokedWithPayload().Which.Headers.Should().BeEquivalentTo(defaultHeaders); - Assert.Null(response.Errors); - Assert.Equal("Luke", response.Data.Human.Name); - } + var defaultHeaders = StarWarsClient.HttpClient.DefaultRequestHeaders; + var response = await StarWarsClient.SendQueryAsync(graphQLRequest, () => new { Human = new { Name = string.Empty } }); + callbackTester.Should().HaveBeenInvokedWithPayload().Which.Headers.Should().BeEquivalentTo(defaultHeaders); + Assert.Null(response.Errors); + Assert.Equal("Luke", response.Data.Human.Name); + } - [Fact] - public async Task PostRequestCanBeCancelled() - { - var graphQLRequest = new GraphQLRequest(@" + [Fact] + public async Task PostRequestCanBeCancelled() + { + var graphQLRequest = new GraphQLRequest(@" query Long { longRunning }"); - var chatQuery = Fixture.Server.Services.GetService(); - var cts = new CancellationTokenSource(); - - var request = - ConcurrentTaskWrapper.New(() => ChatClient.SendQueryAsync(graphQLRequest, () => new { longRunning = string.Empty }, cts.Token)); - - // Test regular request - // start request - request.Start(); - // wait until the query has reached the server - chatQuery.WaitingOnQueryBlocker.Wait(1000).Should().BeTrue("because the request should have reached the server by then"); - // unblock the query - chatQuery.LongRunningQueryBlocker.Set(); - // check execution time - request.Invoke().Result.Data.longRunning.Should().Be("finally returned"); - - // reset stuff - chatQuery.LongRunningQueryBlocker.Reset(); - request.Clear(); - - // cancellation test - request.Start(); - chatQuery.WaitingOnQueryBlocker.Wait(1000).Should().BeTrue("because the request should have reached the server by then"); - cts.Cancel(); - await request.Invoking().Should().ThrowAsync("because the request was cancelled"); - - // let the server finish its query - chatQuery.LongRunningQueryBlocker.Set(); - } + var chatQuery = Fixture.Server.Services.GetService(); + var cts = new CancellationTokenSource(); + + var request = + ConcurrentTaskWrapper.New(() => ChatClient.SendQueryAsync(graphQLRequest, () => new { longRunning = string.Empty }, cts.Token)); + + // Test regular request + // start request + request.Start(); + // wait until the query has reached the server + chatQuery.WaitingOnQueryBlocker.Wait(1000).Should().BeTrue("because the request should have reached the server by then"); + // unblock the query + chatQuery.LongRunningQueryBlocker.Set(); + // check execution time + request.Invoke().Result.Data.longRunning.Should().Be("finally returned"); + + // reset stuff + chatQuery.LongRunningQueryBlocker.Reset(); + request.Clear(); + + // cancellation test + request.Start(); + chatQuery.WaitingOnQueryBlocker.Wait(1000).Should().BeTrue("because the request should have reached the server by then"); + cts.Cancel(); + await request.Invoking().Should().ThrowAsync("because the request was cancelled"); + + // let the server finish its query + chatQuery.LongRunningQueryBlocker.Set(); } } diff --git a/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Newtonsoft.cs b/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Newtonsoft.cs index 7ffcdc11..768970e4 100644 --- a/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Newtonsoft.cs +++ b/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Newtonsoft.cs @@ -1,12 +1,11 @@ using GraphQL.Integration.Tests.Helpers; using Xunit; -namespace GraphQL.Integration.Tests.QueryAndMutationTests +namespace GraphQL.Integration.Tests.QueryAndMutationTests; + +public class Newtonsoft : Base, IClassFixture { - public class Newtonsoft : Base, IClassFixture + public Newtonsoft(NewtonsoftIntegrationServerTestFixture fixture) : base(fixture) { - public Newtonsoft(NewtonsoftIntegrationServerTestFixture fixture) : base(fixture) - { - } } } diff --git a/tests/GraphQL.Integration.Tests/QueryAndMutationTests/SystemTextJson.cs b/tests/GraphQL.Integration.Tests/QueryAndMutationTests/SystemTextJson.cs index e129cfed..9985692c 100644 --- a/tests/GraphQL.Integration.Tests/QueryAndMutationTests/SystemTextJson.cs +++ b/tests/GraphQL.Integration.Tests/QueryAndMutationTests/SystemTextJson.cs @@ -1,12 +1,11 @@ using GraphQL.Integration.Tests.Helpers; using Xunit; -namespace GraphQL.Integration.Tests.QueryAndMutationTests +namespace GraphQL.Integration.Tests.QueryAndMutationTests; + +public class SystemTextJson : Base, IClassFixture { - public class SystemTextJson : Base, IClassFixture + public SystemTextJson(SystemTextJsonIntegrationServerTestFixture fixture) : base(fixture) { - public SystemTextJson(SystemTextJsonIntegrationServerTestFixture fixture) : base(fixture) - { - } } } diff --git a/tests/GraphQL.Integration.Tests/UriExtensionTests.cs b/tests/GraphQL.Integration.Tests/UriExtensionTests.cs index 4daba07c..d218b5d8 100644 --- a/tests/GraphQL.Integration.Tests/UriExtensionTests.cs +++ b/tests/GraphQL.Integration.Tests/UriExtensionTests.cs @@ -2,45 +2,44 @@ using GraphQL.Client.Http; using Xunit; -namespace GraphQL.Integration.Tests +namespace GraphQL.Integration.Tests; + +public class UriExtensionTests { - public class UriExtensionTests + [Theory] + [InlineData("http://thats-not-a-websocket-url.net", false)] + [InlineData("https://thats-not-a-websocket-url.net", false)] + [InlineData("ftp://thats-not-a-websocket-url.net", false)] + [InlineData("ws://that-is-a-websocket-url.net", true)] + [InlineData("wss://that-is-a-websocket-url.net", true)] + [InlineData("WS://that-is-a-websocket-url.net", true)] + [InlineData("WSS://that-is-a-websocket-url.net", true)] + public void HasWebSocketSchemaTest(string url, bool result) { - [Theory] - [InlineData("http://thats-not-a-websocket-url.net", false)] - [InlineData("https://thats-not-a-websocket-url.net", false)] - [InlineData("ftp://thats-not-a-websocket-url.net", false)] - [InlineData("ws://that-is-a-websocket-url.net", true)] - [InlineData("wss://that-is-a-websocket-url.net", true)] - [InlineData("WS://that-is-a-websocket-url.net", true)] - [InlineData("WSS://that-is-a-websocket-url.net", true)] - public void HasWebSocketSchemaTest(string url, bool result) + new Uri(url).HasWebSocketScheme().Should().Be(result); + } + + [Theory] + [InlineData("http://this-url-can-be-converted.net", true, "ws://this-url-can-be-converted.net")] + [InlineData("https://this-url-can-be-converted.net", true, "wss://this-url-can-be-converted.net")] + [InlineData("HTTP://this-url-can-be-converted.net", true, "ws://this-url-can-be-converted.net")] + [InlineData("HTTPS://this-url-can-be-converted.net", true, "wss://this-url-can-be-converted.net")] + [InlineData("ws://this-url-can-be-converted.net", true, "ws://this-url-can-be-converted.net")] + [InlineData("wss://this-url-can-be-converted.net", true, "wss://this-url-can-be-converted.net")] + [InlineData("https://this-url-can-be-converted.net/and/all/elements/?are#preserved", true, "wss://this-url-can-be-converted.net/and/all/elements/?are#preserved")] + [InlineData("ftp://this-url-cannot-be-converted.net", false, null)] + // AppSync example + [InlineData("wss://example1234567890000.appsync-realtime-api.us-west-2.amazonaws.com/graphql?header=123456789ABCDEF&payload=e30=", true, "wss://example1234567890000.appsync-realtime-api.us-west-2.amazonaws.com/graphql?header=123456789ABCDEF&payload=e30=")] + public void GetWebSocketUriTest(string input, bool canConvert, string result) + { + var inputUri = new Uri(input); + if (canConvert) { - new Uri(url).HasWebSocketScheme().Should().Be(result); + inputUri.GetWebSocketUri().Should().BeEquivalentTo(new Uri(result)); } - - [Theory] - [InlineData("http://this-url-can-be-converted.net", true, "ws://this-url-can-be-converted.net")] - [InlineData("https://this-url-can-be-converted.net", true, "wss://this-url-can-be-converted.net")] - [InlineData("HTTP://this-url-can-be-converted.net", true, "ws://this-url-can-be-converted.net")] - [InlineData("HTTPS://this-url-can-be-converted.net", true, "wss://this-url-can-be-converted.net")] - [InlineData("ws://this-url-can-be-converted.net", true, "ws://this-url-can-be-converted.net")] - [InlineData("wss://this-url-can-be-converted.net", true, "wss://this-url-can-be-converted.net")] - [InlineData("https://this-url-can-be-converted.net/and/all/elements/?are#preserved", true, "wss://this-url-can-be-converted.net/and/all/elements/?are#preserved")] - [InlineData("ftp://this-url-cannot-be-converted.net", false, null)] - // AppSync example - [InlineData("wss://example1234567890000.appsync-realtime-api.us-west-2.amazonaws.com/graphql?header=123456789ABCDEF&payload=e30=", true, "wss://example1234567890000.appsync-realtime-api.us-west-2.amazonaws.com/graphql?header=123456789ABCDEF&payload=e30=")] - public void GetWebSocketUriTest(string input, bool canConvert, string result) + else { - var inputUri = new Uri(input); - if (canConvert) - { - inputUri.GetWebSocketUri().Should().BeEquivalentTo(new Uri(result)); - } - else - { - inputUri.Invoking(uri => uri.GetWebSocketUri()).Should().Throw(); - } + inputUri.Invoking(uri => uri.GetWebSocketUri()).Should().Throw(); } } } diff --git a/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs b/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs index a63ed980..35938f34 100644 --- a/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs +++ b/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs @@ -15,471 +15,470 @@ using Xunit; using Xunit.Abstractions; -namespace GraphQL.Integration.Tests.WebsocketTests +namespace GraphQL.Integration.Tests.WebsocketTests; + +public abstract class Base : IAsyncLifetime { - public abstract class Base : IAsyncLifetime + protected readonly ITestOutputHelper Output; + protected readonly IntegrationServerTestFixture Fixture; + protected GraphQLHttpClient ChatClient; + + protected Base(ITestOutputHelper output, IntegrationServerTestFixture fixture) { - protected readonly ITestOutputHelper Output; - protected readonly IntegrationServerTestFixture Fixture; - protected GraphQLHttpClient ChatClient; + Output = output; + Fixture = fixture; + } - protected Base(ITestOutputHelper output, IntegrationServerTestFixture fixture) - { - Output = output; - Fixture = fixture; - } + protected static ReceivedMessage InitialMessage = new ReceivedMessage + { + Content = "initial message", + SentAt = DateTime.Now, + FromId = "1" + }; - protected static ReceivedMessage InitialMessage = new ReceivedMessage - { - Content = "initial message", - SentAt = DateTime.Now, - FromId = "1" - }; + public async Task InitializeAsync() + { + await Fixture.CreateServer(); + // make sure the buffer always contains the same message + Fixture.Server.Services.GetService().AddMessage(InitialMessage); - public async Task InitializeAsync() + if (ChatClient == null) { - await Fixture.CreateServer(); - // make sure the buffer always contains the same message - Fixture.Server.Services.GetService().AddMessage(InitialMessage); - - if (ChatClient == null) - { - // then create the chat client - ChatClient = Fixture.GetChatClient(true); - } + // then create the chat client + ChatClient = Fixture.GetChatClient(true); } + } - public Task DisposeAsync() - { - ChatClient?.Dispose(); - return Task.CompletedTask; - } + public Task DisposeAsync() + { + ChatClient?.Dispose(); + return Task.CompletedTask; + } - [Fact] - public async void CanSendRequestViaWebsocket() - { - await ChatClient.InitializeWebsocketConnection(); - const string message = "some random testing message"; - var response = await ChatClient.AddMessageAsync(message); - response.Errors.Should().BeNullOrEmpty(); - response.Data.AddMessage.Content.Should().Be(message); - } + [Fact] + public async void CanSendRequestViaWebsocket() + { + await ChatClient.InitializeWebsocketConnection(); + const string message = "some random testing message"; + var response = await ChatClient.AddMessageAsync(message); + response.Errors.Should().BeNullOrEmpty(); + response.Data.AddMessage.Content.Should().Be(message); + } - [Fact] - public async void CanUseWebSocketScheme() - { - ChatClient.Options.EndPoint = ChatClient.Options.EndPoint.GetWebSocketUri(); - await ChatClient.InitializeWebsocketConnection(); - const string message = "some random testing message"; - var response = await ChatClient.AddMessageAsync(message); - response.Errors.Should().BeNullOrEmpty(); - response.Data.AddMessage.Content.Should().Be(message); - } - - [Fact] - public async void CanUseDedicatedWebSocketEndpoint() - { - ChatClient.Options.WebSocketEndPoint = ChatClient.Options.EndPoint.GetWebSocketUri(); - ChatClient.Options.EndPoint = new Uri("http://bad-endpoint.test"); - ChatClient.Options.UseWebSocketForQueriesAndMutations = true; - await ChatClient.InitializeWebsocketConnection(); - const string message = "some random testing message"; - var response = await ChatClient.AddMessageAsync(message); - response.Errors.Should().BeNullOrEmpty(); - response.Data.AddMessage.Content.Should().Be(message); - } - - [Fact] - public async void CanUseDedicatedWebSocketEndpointWithoutHttpEndpoint() - { - ChatClient.Options.WebSocketEndPoint = ChatClient.Options.EndPoint.GetWebSocketUri(); - ChatClient.Options.EndPoint = null; - ChatClient.Options.UseWebSocketForQueriesAndMutations = false; - await ChatClient.InitializeWebsocketConnection(); - const string message = "some random testing message"; - var response = await ChatClient.AddMessageAsync(message); - response.Data.AddMessage.Content.Should().Be(message); - } + [Fact] + public async void CanUseWebSocketScheme() + { + ChatClient.Options.EndPoint = ChatClient.Options.EndPoint.GetWebSocketUri(); + await ChatClient.InitializeWebsocketConnection(); + const string message = "some random testing message"; + var response = await ChatClient.AddMessageAsync(message); + response.Errors.Should().BeNullOrEmpty(); + response.Data.AddMessage.Content.Should().Be(message); + } + + [Fact] + public async void CanUseDedicatedWebSocketEndpoint() + { + ChatClient.Options.WebSocketEndPoint = ChatClient.Options.EndPoint.GetWebSocketUri(); + ChatClient.Options.EndPoint = new Uri("http://bad-endpoint.test"); + ChatClient.Options.UseWebSocketForQueriesAndMutations = true; + await ChatClient.InitializeWebsocketConnection(); + const string message = "some random testing message"; + var response = await ChatClient.AddMessageAsync(message); + response.Errors.Should().BeNullOrEmpty(); + response.Data.AddMessage.Content.Should().Be(message); + } + + [Fact] + public async void CanUseDedicatedWebSocketEndpointWithoutHttpEndpoint() + { + ChatClient.Options.WebSocketEndPoint = ChatClient.Options.EndPoint.GetWebSocketUri(); + ChatClient.Options.EndPoint = null; + ChatClient.Options.UseWebSocketForQueriesAndMutations = false; + await ChatClient.InitializeWebsocketConnection(); + const string message = "some random testing message"; + var response = await ChatClient.AddMessageAsync(message); + response.Data.AddMessage.Content.Should().Be(message); + } - [Fact] - public async void WebsocketRequestCanBeCancelled() - { - var graphQLRequest = new GraphQLRequest(@" + [Fact] + public async void WebsocketRequestCanBeCancelled() + { + var graphQLRequest = new GraphQLRequest(@" query Long { longRunning }"); - var chatQuery = Fixture.Server.Services.GetService(); - var cts = new CancellationTokenSource(); - - await ChatClient.InitializeWebsocketConnection(); - var request = - ConcurrentTaskWrapper.New(() => ChatClient.SendQueryAsync(graphQLRequest, () => new { longRunning = string.Empty }, cts.Token)); - - // Test regular request - // start request - request.Start(); - // wait until the query has reached the server - chatQuery.WaitingOnQueryBlocker.Wait(1000).Should().BeTrue("because the request should have reached the server by then"); - // unblock the query - chatQuery.LongRunningQueryBlocker.Set(); - // check execution time - request.Invoke().Result.Data.longRunning.Should().Be("finally returned"); - - // reset stuff - chatQuery.LongRunningQueryBlocker.Reset(); - request.Clear(); - - // cancellation test - request.Start(); - chatQuery.WaitingOnQueryBlocker.Wait(1000).Should().BeTrue("because the request should have reached the server by then"); - cts.Cancel(); - await request.Invoking().Should().ThrowAsync("because the request was cancelled"); - - // let the server finish its query - chatQuery.LongRunningQueryBlocker.Set(); - } + var chatQuery = Fixture.Server.Services.GetService(); + var cts = new CancellationTokenSource(); + + await ChatClient.InitializeWebsocketConnection(); + var request = + ConcurrentTaskWrapper.New(() => ChatClient.SendQueryAsync(graphQLRequest, () => new { longRunning = string.Empty }, cts.Token)); + + // Test regular request + // start request + request.Start(); + // wait until the query has reached the server + chatQuery.WaitingOnQueryBlocker.Wait(1000).Should().BeTrue("because the request should have reached the server by then"); + // unblock the query + chatQuery.LongRunningQueryBlocker.Set(); + // check execution time + request.Invoke().Result.Data.longRunning.Should().Be("finally returned"); + + // reset stuff + chatQuery.LongRunningQueryBlocker.Reset(); + request.Clear(); + + // cancellation test + request.Start(); + chatQuery.WaitingOnQueryBlocker.Wait(1000).Should().BeTrue("because the request should have reached the server by then"); + cts.Cancel(); + await request.Invoking().Should().ThrowAsync("because the request was cancelled"); + + // let the server finish its query + chatQuery.LongRunningQueryBlocker.Set(); + } - [Fact] - public async void CanHandleRequestErrorViaWebsocket() - { - await ChatClient.InitializeWebsocketConnection(); - var response = await ChatClient.SendQueryAsync("this query is formatted quite badly"); - response.Errors.Should().ContainSingle("because the query is invalid"); - } + [Fact] + public async void CanHandleRequestErrorViaWebsocket() + { + await ChatClient.InitializeWebsocketConnection(); + var response = await ChatClient.SendQueryAsync("this query is formatted quite badly"); + response.Errors.Should().ContainSingle("because the query is invalid"); + } - private const string SUBSCRIPTION_QUERY = @" + private const string SUBSCRIPTION_QUERY = @" subscription { messageAdded{ content } }"; - private readonly GraphQLRequest _subscriptionRequest = new GraphQLRequest(SUBSCRIPTION_QUERY); + private readonly GraphQLRequest _subscriptionRequest = new GraphQLRequest(SUBSCRIPTION_QUERY); - [Fact] - public async void CanCreateObservableSubscription() - { - var callbackMonitor = ChatClient.ConfigureMonitorForOnWebsocketConnected(); - await ChatClient.InitializeWebsocketConnection(); - callbackMonitor.Should().HaveBeenInvokedWithPayload(); + [Fact] + public async void CanCreateObservableSubscription() + { + var callbackMonitor = ChatClient.ConfigureMonitorForOnWebsocketConnected(); + await ChatClient.InitializeWebsocketConnection(); + callbackMonitor.Should().HaveBeenInvokedWithPayload(); + + Debug.WriteLine("creating subscription stream"); + var observable = ChatClient.CreateSubscriptionStream(_subscriptionRequest); + + Debug.WriteLine("subscribing..."); + using var observer = observable.Observe(); + await observer.Should().PushAsync(1); + observer.RecordedMessages.Last().Errors.Should().BeNullOrEmpty(); + observer.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(InitialMessage.Content); + + const string message1 = "Hello World"; + var response = await ChatClient.AddMessageAsync(message1); + response.Errors.Should().BeNullOrEmpty(); + response.Data.AddMessage.Content.Should().Be(message1); + await observer.Should().PushAsync(2); + observer.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(message1); + + const string message2 = "lorem ipsum dolor si amet"; + response = await ChatClient.AddMessageAsync(message2); + response.Data.AddMessage.Content.Should().Be(message2); + await observer.Should().PushAsync(3); + observer.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(message2); + + // disposing the client should throw a TaskCanceledException on the subscription + ChatClient.Dispose(); + await observer.Should().CompleteAsync(); + } - Debug.WriteLine("creating subscription stream"); - var observable = ChatClient.CreateSubscriptionStream(_subscriptionRequest); + public class MessageAddedSubscriptionResult + { + public MessageAddedContent MessageAdded { get; set; } - Debug.WriteLine("subscribing..."); - using var observer = observable.Observe(); - await observer.Should().PushAsync(1); - observer.RecordedMessages.Last().Errors.Should().BeNullOrEmpty(); - observer.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(InitialMessage.Content); + public class MessageAddedContent + { + public string Content { get; set; } + } + } - const string message1 = "Hello World"; - var response = await ChatClient.AddMessageAsync(message1); - response.Errors.Should().BeNullOrEmpty(); - response.Data.AddMessage.Content.Should().Be(message1); - await observer.Should().PushAsync(2); - observer.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(message1); + [Fact] + public async void CanReconnectWithSameObservable() + { + var callbackMonitor = ChatClient.ConfigureMonitorForOnWebsocketConnected(); + + Debug.WriteLine("creating subscription stream"); + var observable = ChatClient.CreateSubscriptionStream(_subscriptionRequest); + + Debug.WriteLine("subscribing..."); + var observer = observable.Observe(); + callbackMonitor.Should().HaveBeenInvokedWithPayload(); + await ChatClient.InitializeWebsocketConnection(); + Debug.WriteLine("websocket connection initialized"); + await observer.Should().PushAsync(1); + observer.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(InitialMessage.Content); + + const string message1 = "Hello World"; + Debug.WriteLine($"adding message {message1}"); + var response = await ChatClient.AddMessageAsync(message1); + response.Data.AddMessage.Content.Should().Be(message1); + await observer.Should().PushAsync(2); + observer.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(message1); + + const string message2 = "How are you?"; + response = await ChatClient.AddMessageAsync(message2); + response.Data.AddMessage.Content.Should().Be(message2); + await observer.Should().PushAsync(3); + observer.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(message2); + + Debug.WriteLine("disposing subscription..."); + observer.Dispose(); // does not close the websocket connection + + Debug.WriteLine($"creating new subscription from thread {Thread.CurrentThread.ManagedThreadId} ..."); + var observer2 = observable.Observe(); + Debug.WriteLine($"waiting for payload on {Thread.CurrentThread.ManagedThreadId} ..."); + await observer2.Should().PushAsync(1); + observer2.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(message2); + + const string message3 = "lorem ipsum dolor si amet"; + response = await ChatClient.AddMessageAsync(message3); + response.Data.AddMessage.Content.Should().Be(message3); + await observer2.Should().PushAsync(2); + observer2.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(message3); + + // disposing the client should complete the subscription + ChatClient.Dispose(); + await observer2.Should().CompleteAsync(); + observer2.Dispose(); + } - const string message2 = "lorem ipsum dolor si amet"; - response = await ChatClient.AddMessageAsync(message2); - response.Data.AddMessage.Content.Should().Be(message2); - await observer.Should().PushAsync(3); - observer.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(message2); + private const string SUBSCRIPTION_QUERY2 = @" + subscription { + userJoined{ + displayName + id + } + }"; - // disposing the client should throw a TaskCanceledException on the subscription - ChatClient.Dispose(); - await observer.Should().CompleteAsync(); - } + public class UserJoinedSubscriptionResult + { + public UserJoinedContent UserJoined { get; set; } - public class MessageAddedSubscriptionResult + public class UserJoinedContent { - public MessageAddedContent MessageAdded { get; set; } + public string DisplayName { get; set; } - public class MessageAddedContent - { - public string Content { get; set; } - } + public string Id { get; set; } } - [Fact] - public async void CanReconnectWithSameObservable() - { - var callbackMonitor = ChatClient.ConfigureMonitorForOnWebsocketConnected(); + } - Debug.WriteLine("creating subscription stream"); - var observable = ChatClient.CreateSubscriptionStream(_subscriptionRequest); + private readonly GraphQLRequest _subscriptionRequest2 = new GraphQLRequest(SUBSCRIPTION_QUERY2); - Debug.WriteLine("subscribing..."); - var observer = observable.Observe(); - callbackMonitor.Should().HaveBeenInvokedWithPayload(); - await ChatClient.InitializeWebsocketConnection(); - Debug.WriteLine("websocket connection initialized"); - await observer.Should().PushAsync(1); - observer.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(InitialMessage.Content); + [Fact] + public async void CanConnectTwoSubscriptionsSimultaneously() + { + var port = NetworkHelpers.GetFreeTcpPortNumber(); + var callbackTester = new CallbackMonitor(); + var callbackTester2 = new CallbackMonitor(); - const string message1 = "Hello World"; - Debug.WriteLine($"adding message {message1}"); - var response = await ChatClient.AddMessageAsync(message1); - response.Data.AddMessage.Content.Should().Be(message1); - await observer.Should().PushAsync(2); - observer.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(message1); + var callbackMonitor = ChatClient.ConfigureMonitorForOnWebsocketConnected(); + await ChatClient.InitializeWebsocketConnection(); + callbackMonitor.Should().HaveBeenInvokedWithPayload(); - const string message2 = "How are you?"; - response = await ChatClient.AddMessageAsync(message2); - response.Data.AddMessage.Content.Should().Be(message2); - await observer.Should().PushAsync(3); - observer.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(message2); + Debug.WriteLine("creating subscription stream"); + var observable1 = ChatClient.CreateSubscriptionStream(_subscriptionRequest, callbackTester.Invoke); + var observable2 = ChatClient.CreateSubscriptionStream(_subscriptionRequest2, callbackTester2.Invoke); - Debug.WriteLine("disposing subscription..."); - observer.Dispose(); // does not close the websocket connection + Debug.WriteLine("subscribing..."); + var blocker = new ManualResetEventSlim(false); + FluentTestObserver> messagesMonitor = null; + FluentTestObserver> joinedMonitor = null; - Debug.WriteLine($"creating new subscription from thread {Thread.CurrentThread.ManagedThreadId} ..."); - var observer2 = observable.Observe(); - Debug.WriteLine($"waiting for payload on {Thread.CurrentThread.ManagedThreadId} ..."); - await observer2.Should().PushAsync(1); - observer2.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(message2); + var tasks = new List + { + Task.Run(() => + { + blocker.Wait(); + messagesMonitor = observable1.Observe(); + }), + Task.Run(() => + { + blocker.Wait(); + joinedMonitor = observable2.Observe(); + }) + }; - const string message3 = "lorem ipsum dolor si amet"; - response = await ChatClient.AddMessageAsync(message3); - response.Data.AddMessage.Content.Should().Be(message3); - await observer2.Should().PushAsync(2); - observer2.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(message3); + blocker.Set(); + await Task.WhenAll(tasks); - // disposing the client should complete the subscription - ChatClient.Dispose(); - await observer2.Should().CompleteAsync(); - observer2.Dispose(); - } + await messagesMonitor.Should().PushAsync(1); + messagesMonitor.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(InitialMessage.Content); - private const string SUBSCRIPTION_QUERY2 = @" - subscription { - userJoined{ - displayName - id - } - }"; + const string message1 = "Hello World"; + var response = await ChatClient.AddMessageAsync(message1); + response.Data.AddMessage.Content.Should().Be(message1); + await messagesMonitor.Should().PushAsync(2); + messagesMonitor.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(message1); + + joinedMonitor.Should().NotPush(); + messagesMonitor.Clear(); + joinedMonitor.Clear(); - public class UserJoinedSubscriptionResult + var joinResponse = await ChatClient.JoinDeveloperUser(); + joinResponse.Data.Join.DisplayName.Should().Be("developer", "because that's the display name of user \"1\""); + + var payload = await joinedMonitor.Should().PushAsync().GetLastMessageAsync(); + using (new AssertionScope()) { - public UserJoinedContent UserJoined { get; set; } + payload.Data.UserJoined.Id.Should().Be("1", "because that's the id we sent with our mutation request"); + payload.Data.UserJoined.DisplayName.Should().Be("developer", "because that's the display name of user \"1\""); + } - public class UserJoinedContent - { - public string DisplayName { get; set; } + messagesMonitor.Should().NotPush(); + messagesMonitor.Clear(); + joinedMonitor.Clear(); - public string Id { get; set; } - } + Debug.WriteLine("disposing subscription..."); + joinedMonitor.Dispose(); - } + const string message3 = "lorem ipsum dolor si amet"; + response = await ChatClient.AddMessageAsync(message3); + response.Data.AddMessage.Content.Should().Be(message3); + var msg = await messagesMonitor.Should().PushAsync().GetLastMessageAsync(); + msg.Data.MessageAdded.Content.Should().Be(message3); - private readonly GraphQLRequest _subscriptionRequest2 = new GraphQLRequest(SUBSCRIPTION_QUERY2); + // disposing the client should complete the subscription + ChatClient.Dispose(); + await messagesMonitor.Should().CompleteAsync(); + } - [Fact] - public async void CanConnectTwoSubscriptionsSimultaneously() + + [Fact] + public async void CanHandleConnectionTimeout() + { + var errorMonitor = new CallbackMonitor(); + var reconnectBlocker = new ManualResetEventSlim(false); + + var callbackMonitor = ChatClient.ConfigureMonitorForOnWebsocketConnected(); + // configure back-off strategy to allow it to be controlled from within the unit test + ChatClient.Options.BackOffStrategy = i => { - var port = NetworkHelpers.GetFreeTcpPortNumber(); - var callbackTester = new CallbackMonitor(); - var callbackTester2 = new CallbackMonitor(); + Debug.WriteLine("back-off strategy: waiting on reconnect blocker"); + reconnectBlocker.Wait(); + Debug.WriteLine("back-off strategy: reconnecting..."); + return TimeSpan.Zero; + }; - var callbackMonitor = ChatClient.ConfigureMonitorForOnWebsocketConnected(); - await ChatClient.InitializeWebsocketConnection(); - callbackMonitor.Should().HaveBeenInvokedWithPayload(); + var websocketStates = new ConcurrentQueue(); + + using (ChatClient.WebsocketConnectionState.Subscribe(websocketStates.Enqueue)) + { + websocketStates.Should().ContainSingle(state => state == GraphQLWebsocketConnectionState.Disconnected); + Debug.WriteLine($"Test method thread id: {Thread.CurrentThread.ManagedThreadId}"); Debug.WriteLine("creating subscription stream"); - var observable1 = ChatClient.CreateSubscriptionStream(_subscriptionRequest, callbackTester.Invoke); - var observable2 = ChatClient.CreateSubscriptionStream(_subscriptionRequest2, callbackTester2.Invoke); + var observable = ChatClient.CreateSubscriptionStream(_subscriptionRequest, errorMonitor.Invoke); Debug.WriteLine("subscribing..."); - var blocker = new ManualResetEventSlim(false); - FluentTestObserver> messagesMonitor = null; - FluentTestObserver> joinedMonitor = null; + var observer = observable.Observe(); + callbackMonitor.Should().HaveBeenInvokedWithPayload(); - var tasks = new List - { - Task.Run(() => - { - blocker.Wait(); - messagesMonitor = observable1.Observe(); - }), - Task.Run(() => - { - blocker.Wait(); - joinedMonitor = observable2.Observe(); - }) - }; - - blocker.Set(); - await Task.WhenAll(tasks); - - await messagesMonitor.Should().PushAsync(1); - messagesMonitor.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(InitialMessage.Content); + websocketStates.Should().ContainInOrder( + GraphQLWebsocketConnectionState.Disconnected, + GraphQLWebsocketConnectionState.Connecting, + GraphQLWebsocketConnectionState.Connected); + // clear the collection so the next tests on the collection work as expected + websocketStates.Clear(); + + await observer.Should().PushAsync(1); + observer.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(InitialMessage.Content); const string message1 = "Hello World"; var response = await ChatClient.AddMessageAsync(message1); response.Data.AddMessage.Content.Should().Be(message1); - await messagesMonitor.Should().PushAsync(2); - messagesMonitor.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(message1); - - joinedMonitor.Should().NotPush(); - messagesMonitor.Clear(); - joinedMonitor.Clear(); - - var joinResponse = await ChatClient.JoinDeveloperUser(); - joinResponse.Data.Join.DisplayName.Should().Be("developer", "because that's the display name of user \"1\""); + await observer.Should().PushAsync(2); + observer.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(message1); - var payload = await joinedMonitor.Should().PushAsync().GetLastMessageAsync(); - using (new AssertionScope()) - { - payload.Data.UserJoined.Id.Should().Be("1", "because that's the id we sent with our mutation request"); - payload.Data.UserJoined.DisplayName.Should().Be("developer", "because that's the display name of user \"1\""); - } + Debug.WriteLine("stopping web host..."); + await Fixture.ShutdownServer(); + Debug.WriteLine("web host stopped"); - messagesMonitor.Should().NotPush(); - messagesMonitor.Clear(); - joinedMonitor.Clear(); + errorMonitor.Should().HaveBeenInvokedWithPayload(10.Seconds()) + .Which.Should().BeOfType(); + websocketStates.Should().Contain(GraphQLWebsocketConnectionState.Disconnected); - Debug.WriteLine("disposing subscription..."); - joinedMonitor.Dispose(); + Debug.WriteLine("restarting web host..."); + await InitializeAsync(); + Debug.WriteLine("web host started"); + reconnectBlocker.Set(); + callbackMonitor.Should().HaveBeenInvokedWithPayload(3.Seconds()); + await observer.Should().PushAsync(3); + observer.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(InitialMessage.Content); - const string message3 = "lorem ipsum dolor si amet"; - response = await ChatClient.AddMessageAsync(message3); - response.Data.AddMessage.Content.Should().Be(message3); - var msg = await messagesMonitor.Should().PushAsync().GetLastMessageAsync(); - msg.Data.MessageAdded.Content.Should().Be(message3); + websocketStates.Should().ContainInOrder( + GraphQLWebsocketConnectionState.Disconnected, + GraphQLWebsocketConnectionState.Connecting, + GraphQLWebsocketConnectionState.Connected); // disposing the client should complete the subscription ChatClient.Dispose(); - await messagesMonitor.Should().CompleteAsync(); - } - - - [Fact] - public async void CanHandleConnectionTimeout() - { - var errorMonitor = new CallbackMonitor(); - var reconnectBlocker = new ManualResetEventSlim(false); - - var callbackMonitor = ChatClient.ConfigureMonitorForOnWebsocketConnected(); - // configure back-off strategy to allow it to be controlled from within the unit test - ChatClient.Options.BackOffStrategy = i => - { - Debug.WriteLine("back-off strategy: waiting on reconnect blocker"); - reconnectBlocker.Wait(); - Debug.WriteLine("back-off strategy: reconnecting..."); - return TimeSpan.Zero; - }; - - var websocketStates = new ConcurrentQueue(); - - using (ChatClient.WebsocketConnectionState.Subscribe(websocketStates.Enqueue)) - { - websocketStates.Should().ContainSingle(state => state == GraphQLWebsocketConnectionState.Disconnected); - - Debug.WriteLine($"Test method thread id: {Thread.CurrentThread.ManagedThreadId}"); - Debug.WriteLine("creating subscription stream"); - var observable = ChatClient.CreateSubscriptionStream(_subscriptionRequest, errorMonitor.Invoke); - - Debug.WriteLine("subscribing..."); - var observer = observable.Observe(); - callbackMonitor.Should().HaveBeenInvokedWithPayload(); - - websocketStates.Should().ContainInOrder( - GraphQLWebsocketConnectionState.Disconnected, - GraphQLWebsocketConnectionState.Connecting, - GraphQLWebsocketConnectionState.Connected); - // clear the collection so the next tests on the collection work as expected - websocketStates.Clear(); - - await observer.Should().PushAsync(1); - observer.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(InitialMessage.Content); - - const string message1 = "Hello World"; - var response = await ChatClient.AddMessageAsync(message1); - response.Data.AddMessage.Content.Should().Be(message1); - await observer.Should().PushAsync(2); - observer.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(message1); - - Debug.WriteLine("stopping web host..."); - await Fixture.ShutdownServer(); - Debug.WriteLine("web host stopped"); - - errorMonitor.Should().HaveBeenInvokedWithPayload(10.Seconds()) - .Which.Should().BeOfType(); - websocketStates.Should().Contain(GraphQLWebsocketConnectionState.Disconnected); - - Debug.WriteLine("restarting web host..."); - await InitializeAsync(); - Debug.WriteLine("web host started"); - reconnectBlocker.Set(); - callbackMonitor.Should().HaveBeenInvokedWithPayload(3.Seconds()); - await observer.Should().PushAsync(3); - observer.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(InitialMessage.Content); - - websocketStates.Should().ContainInOrder( - GraphQLWebsocketConnectionState.Disconnected, - GraphQLWebsocketConnectionState.Connecting, - GraphQLWebsocketConnectionState.Connected); - - // disposing the client should complete the subscription - ChatClient.Dispose(); - await observer.Should().CompleteAsync(5.Seconds()); - } + await observer.Should().CompleteAsync(5.Seconds()); } + } - [Fact] - public async void CanHandleSubscriptionError() - { - var callbackMonitor = ChatClient.ConfigureMonitorForOnWebsocketConnected(); - await ChatClient.InitializeWebsocketConnection(); - callbackMonitor.Should().HaveBeenInvokedWithPayload(); - Debug.WriteLine("creating subscription stream"); - var observable = ChatClient.CreateSubscriptionStream( - new GraphQLRequest(@" + [Fact] + public async void CanHandleSubscriptionError() + { + var callbackMonitor = ChatClient.ConfigureMonitorForOnWebsocketConnected(); + await ChatClient.InitializeWebsocketConnection(); + callbackMonitor.Should().HaveBeenInvokedWithPayload(); + Debug.WriteLine("creating subscription stream"); + var observable = ChatClient.CreateSubscriptionStream( + new GraphQLRequest(@" subscription { failImmediately { content } }") - ); + ); - Debug.WriteLine("subscribing..."); + Debug.WriteLine("subscribing..."); - using var observer = observable.Observe(); + using var observer = observable.Observe(); - await observer.Should().PushAsync(); - observer.RecordedMessages.Last().Errors.Should().ContainSingle(); + await observer.Should().PushAsync(); + observer.RecordedMessages.Last().Errors.Should().ContainSingle(); - await observer.Should().CompleteAsync(); - ChatClient.Dispose(); - } + await observer.Should().CompleteAsync(); + ChatClient.Dispose(); + } - [Fact] - public async void CanHandleQueryErrorInSubscription() - { - var callbackMonitor = ChatClient.ConfigureMonitorForOnWebsocketConnected(); - await ChatClient.InitializeWebsocketConnection(); - callbackMonitor.Should().HaveBeenInvokedWithPayload(); - Debug.WriteLine("creating subscription stream"); - var observable = ChatClient.CreateSubscriptionStream( - new GraphQLRequest(@" + [Fact] + public async void CanHandleQueryErrorInSubscription() + { + var callbackMonitor = ChatClient.ConfigureMonitorForOnWebsocketConnected(); + await ChatClient.InitializeWebsocketConnection(); + callbackMonitor.Should().HaveBeenInvokedWithPayload(); + Debug.WriteLine("creating subscription stream"); + var observable = ChatClient.CreateSubscriptionStream( + new GraphQLRequest(@" subscription { fieldDoesNotExist { content } }") - ); + ); - Debug.WriteLine("subscribing..."); + Debug.WriteLine("subscribing..."); - using var observer = observable.Observe(); + using var observer = observable.Observe(); - await observer.Should().PushAsync(); - observer.RecordedMessages.Last().Errors.Should().ContainSingle(); - - await observer.Should().CompleteAsync(); - ChatClient.Dispose(); - } + await observer.Should().PushAsync(); + observer.RecordedMessages.Last().Errors.Should().ContainSingle(); + await observer.Should().CompleteAsync(); + ChatClient.Dispose(); } + } diff --git a/tests/GraphQL.Integration.Tests/WebsocketTests/Newtonsoft.cs b/tests/GraphQL.Integration.Tests/WebsocketTests/Newtonsoft.cs index d77ca14c..87c23ba6 100644 --- a/tests/GraphQL.Integration.Tests/WebsocketTests/Newtonsoft.cs +++ b/tests/GraphQL.Integration.Tests/WebsocketTests/Newtonsoft.cs @@ -2,12 +2,11 @@ using Xunit; using Xunit.Abstractions; -namespace GraphQL.Integration.Tests.WebsocketTests +namespace GraphQL.Integration.Tests.WebsocketTests; + +public class Newtonsoft : Base, IClassFixture { - public class Newtonsoft : Base, IClassFixture + public Newtonsoft(ITestOutputHelper output, NewtonsoftIntegrationServerTestFixture fixture) : base(output, fixture) { - public Newtonsoft(ITestOutputHelper output, NewtonsoftIntegrationServerTestFixture fixture) : base(output, fixture) - { - } } } diff --git a/tests/GraphQL.Integration.Tests/WebsocketTests/SystemTextJson.cs b/tests/GraphQL.Integration.Tests/WebsocketTests/SystemTextJson.cs index ab4659a4..e3648bf4 100644 --- a/tests/GraphQL.Integration.Tests/WebsocketTests/SystemTextJson.cs +++ b/tests/GraphQL.Integration.Tests/WebsocketTests/SystemTextJson.cs @@ -2,12 +2,11 @@ using Xunit; using Xunit.Abstractions; -namespace GraphQL.Integration.Tests.WebsocketTests +namespace GraphQL.Integration.Tests.WebsocketTests; + +public class SystemTextJson : Base, IClassFixture { - public class SystemTextJson : Base, IClassFixture + public SystemTextJson(ITestOutputHelper output, SystemTextJsonIntegrationServerTestFixture fixture) : base(output, fixture) { - public SystemTextJson(ITestOutputHelper output, SystemTextJsonIntegrationServerTestFixture fixture) : base(output, fixture) - { - } } } diff --git a/tests/GraphQL.Primitives.Tests/GraphQLLocationTest.cs b/tests/GraphQL.Primitives.Tests/GraphQLLocationTest.cs index 3d7fde8d..9f2248c6 100644 --- a/tests/GraphQL.Primitives.Tests/GraphQLLocationTest.cs +++ b/tests/GraphQL.Primitives.Tests/GraphQLLocationTest.cs @@ -1,62 +1,61 @@ using Xunit; -namespace GraphQL.Primitives.Tests +namespace GraphQL.Primitives.Tests; + +public class GraphQLLocationTest { - public class GraphQLLocationTest + [Fact] + public void ConstructorFact() + { + var graphQLLocation = new GraphQLLocation { Column = 1, Line = 2 }; + Assert.Equal(1U, graphQLLocation.Column); + Assert.Equal(2U, graphQLLocation.Line); + } + + [Fact] + public void Equality1Fact() + { + var graphQLLocation = new GraphQLLocation { Column = 1, Line = 2 }; + Assert.Equal(graphQLLocation, graphQLLocation); + } + + [Fact] + public void Equality2Fact() + { + var graphQLLocation1 = new GraphQLLocation { Column = 1, Line = 2 }; + var graphQLLocation2 = new GraphQLLocation { Column = 1, Line = 2 }; + Assert.Equal(graphQLLocation1, graphQLLocation2); + } + + [Fact] + public void EqualityOperatorFact() + { + var graphQLLocation1 = new GraphQLLocation { Column = 1, Line = 2 }; + var graphQLLocation2 = new GraphQLLocation { Column = 1, Line = 2 }; + Assert.True(graphQLLocation1 == graphQLLocation2); + } + + [Fact] + public void InEqualityFact() + { + var graphQLLocation1 = new GraphQLLocation { Column = 1, Line = 2 }; + var graphQLLocation2 = new GraphQLLocation { Column = 2, Line = 1 }; + Assert.NotEqual(graphQLLocation1, graphQLLocation2); + } + + [Fact] + public void InEqualityOperatorFact() + { + var graphQLLocation1 = new GraphQLLocation { Column = 1, Line = 2 }; + var graphQLLocation2 = new GraphQLLocation { Column = 2, Line = 1 }; + Assert.True(graphQLLocation1 != graphQLLocation2); + } + + [Fact] + public void GetHashCodeFact() { - [Fact] - public void ConstructorFact() - { - var graphQLLocation = new GraphQLLocation { Column = 1, Line = 2 }; - Assert.Equal(1U, graphQLLocation.Column); - Assert.Equal(2U, graphQLLocation.Line); - } - - [Fact] - public void Equality1Fact() - { - var graphQLLocation = new GraphQLLocation { Column = 1, Line = 2 }; - Assert.Equal(graphQLLocation, graphQLLocation); - } - - [Fact] - public void Equality2Fact() - { - var graphQLLocation1 = new GraphQLLocation { Column = 1, Line = 2 }; - var graphQLLocation2 = new GraphQLLocation { Column = 1, Line = 2 }; - Assert.Equal(graphQLLocation1, graphQLLocation2); - } - - [Fact] - public void EqualityOperatorFact() - { - var graphQLLocation1 = new GraphQLLocation { Column = 1, Line = 2 }; - var graphQLLocation2 = new GraphQLLocation { Column = 1, Line = 2 }; - Assert.True(graphQLLocation1 == graphQLLocation2); - } - - [Fact] - public void InEqualityFact() - { - var graphQLLocation1 = new GraphQLLocation { Column = 1, Line = 2 }; - var graphQLLocation2 = new GraphQLLocation { Column = 2, Line = 1 }; - Assert.NotEqual(graphQLLocation1, graphQLLocation2); - } - - [Fact] - public void InEqualityOperatorFact() - { - var graphQLLocation1 = new GraphQLLocation { Column = 1, Line = 2 }; - var graphQLLocation2 = new GraphQLLocation { Column = 2, Line = 1 }; - Assert.True(graphQLLocation1 != graphQLLocation2); - } - - [Fact] - public void GetHashCodeFact() - { - var graphQLLocation1 = new GraphQLLocation { Column = 1, Line = 2 }; - var graphQLLocation2 = new GraphQLLocation { Column = 1, Line = 2 }; - Assert.True(graphQLLocation1.GetHashCode() == graphQLLocation2.GetHashCode()); - } + var graphQLLocation1 = new GraphQLLocation { Column = 1, Line = 2 }; + var graphQLLocation2 = new GraphQLLocation { Column = 1, Line = 2 }; + Assert.True(graphQLLocation1.GetHashCode() == graphQLLocation2.GetHashCode()); } } diff --git a/tests/GraphQL.Primitives.Tests/GraphQLRequestTest.cs b/tests/GraphQL.Primitives.Tests/GraphQLRequestTest.cs index e5b41a15..b56e6376 100644 --- a/tests/GraphQL.Primitives.Tests/GraphQLRequestTest.cs +++ b/tests/GraphQL.Primitives.Tests/GraphQLRequestTest.cs @@ -1,183 +1,182 @@ using Xunit; -namespace GraphQL.Primitives.Tests +namespace GraphQL.Primitives.Tests; + +public class GraphQLRequestTest { - public class GraphQLRequestTest + [Fact] + public void ConstructorFact() { - [Fact] - public void ConstructorFact() - { - var graphQLRequest = new GraphQLRequest("{hero{name}}"); - Assert.NotNull(graphQLRequest.Query); - Assert.Null(graphQLRequest.OperationName); - Assert.Null(graphQLRequest.Variables); - } - - [Fact] - public void ConstructorExtendedFact() - { - var graphQLRequest = new GraphQLRequest("{hero{name}}", new { varName = "varValue" }, "operationName"); - Assert.NotNull(graphQLRequest.Query); - Assert.NotNull(graphQLRequest.OperationName); - Assert.NotNull(graphQLRequest.Variables); - } - - [Fact] - public void Equality1Fact() - { - var graphQLRequest = new GraphQLRequest("{hero{name}}"); - Assert.Equal(graphQLRequest, graphQLRequest); - } + var graphQLRequest = new GraphQLRequest("{hero{name}}"); + Assert.NotNull(graphQLRequest.Query); + Assert.Null(graphQLRequest.OperationName); + Assert.Null(graphQLRequest.Variables); + } - [Fact] - public void Equality2Fact() - { - var graphQLRequest1 = new GraphQLRequest("{hero{name}}"); - var graphQLRequest2 = new GraphQLRequest("{hero{name}}"); - Assert.Equal(graphQLRequest1, graphQLRequest2); - } + [Fact] + public void ConstructorExtendedFact() + { + var graphQLRequest = new GraphQLRequest("{hero{name}}", new { varName = "varValue" }, "operationName"); + Assert.NotNull(graphQLRequest.Query); + Assert.NotNull(graphQLRequest.OperationName); + Assert.NotNull(graphQLRequest.Variables); + } - [Fact] - public void Equality3Fact() - { - var graphQLRequest1 = new GraphQLRequest("{hero{name}}", new { varName = "varValue" }, "operationName"); - var graphQLRequest2 = new GraphQLRequest("{hero{name}}", new { varName = "varValue" }, "operationName"); - Assert.Equal(graphQLRequest1, graphQLRequest2); - } + [Fact] + public void Equality1Fact() + { + var graphQLRequest = new GraphQLRequest("{hero{name}}"); + Assert.Equal(graphQLRequest, graphQLRequest); + } - [Fact] - public void Equality4Fact() - { - var graphQLRequest1 = new GraphQLRequest("{hero{name}}", new { varName = "varValue1" }, "operationName"); - var graphQLRequest2 = new GraphQLRequest("{hero{name}}", new { varName = "varValue2" }, "operationName"); - Assert.NotEqual(graphQLRequest1, graphQLRequest2); - } + [Fact] + public void Equality2Fact() + { + var graphQLRequest1 = new GraphQLRequest("{hero{name}}"); + var graphQLRequest2 = new GraphQLRequest("{hero{name}}"); + Assert.Equal(graphQLRequest1, graphQLRequest2); + } - [Fact] - public void EqualityOperatorFact() - { - var graphQLRequest1 = new GraphQLRequest("{hero{name}}", new { varName = "varValue" }, "operationName"); - var graphQLRequest2 = new GraphQLRequest("{hero{name}}", new { varName = "varValue" }, "operationName"); - Assert.True(graphQLRequest1 == graphQLRequest2); - } + [Fact] + public void Equality3Fact() + { + var graphQLRequest1 = new GraphQLRequest("{hero{name}}", new { varName = "varValue" }, "operationName"); + var graphQLRequest2 = new GraphQLRequest("{hero{name}}", new { varName = "varValue" }, "operationName"); + Assert.Equal(graphQLRequest1, graphQLRequest2); + } - [Fact] - public void InEquality1Fact() - { - var graphQLRequest1 = new GraphQLRequest("{hero{name1}}"); - var graphQLRequest2 = new GraphQLRequest("{hero{name2}}"); - Assert.NotEqual(graphQLRequest1, graphQLRequest2); - } + [Fact] + public void Equality4Fact() + { + var graphQLRequest1 = new GraphQLRequest("{hero{name}}", new { varName = "varValue1" }, "operationName"); + var graphQLRequest2 = new GraphQLRequest("{hero{name}}", new { varName = "varValue2" }, "operationName"); + Assert.NotEqual(graphQLRequest1, graphQLRequest2); + } - [Fact] - public void InEquality2Fact() - { - var graphQLRequest1 = new GraphQLRequest("{hero{name}}", new { varName = "varValue1" }, "operationName"); - var graphQLRequest2 = new GraphQLRequest("{hero{name}}", new { varName = "varValue2" }, "operationName"); - Assert.NotEqual(graphQLRequest1, graphQLRequest2); - } + [Fact] + public void EqualityOperatorFact() + { + var graphQLRequest1 = new GraphQLRequest("{hero{name}}", new { varName = "varValue" }, "operationName"); + var graphQLRequest2 = new GraphQLRequest("{hero{name}}", new { varName = "varValue" }, "operationName"); + Assert.True(graphQLRequest1 == graphQLRequest2); + } - [Fact] - public void InEqualityOperatorFact() - { - var graphQLRequest1 = new GraphQLRequest("{hero{name1}}"); - var graphQLRequest2 = new GraphQLRequest("{hero{name2}}"); - Assert.True(graphQLRequest1 != graphQLRequest2); - } + [Fact] + public void InEquality1Fact() + { + var graphQLRequest1 = new GraphQLRequest("{hero{name1}}"); + var graphQLRequest2 = new GraphQLRequest("{hero{name2}}"); + Assert.NotEqual(graphQLRequest1, graphQLRequest2); + } - [Fact] - public void GetHashCode1Fact() - { - var graphQLRequest1 = new GraphQLRequest("{hero{name}}"); - var graphQLRequest2 = new GraphQLRequest("{hero{name}}"); - Assert.True(graphQLRequest1.GetHashCode() == graphQLRequest2.GetHashCode()); - } + [Fact] + public void InEquality2Fact() + { + var graphQLRequest1 = new GraphQLRequest("{hero{name}}", new { varName = "varValue1" }, "operationName"); + var graphQLRequest2 = new GraphQLRequest("{hero{name}}", new { varName = "varValue2" }, "operationName"); + Assert.NotEqual(graphQLRequest1, graphQLRequest2); + } - [Fact] - public void GetHashCode2Fact() - { - var graphQLRequest1 = new GraphQLRequest("{hero{name}}", new { varName = "varValue" }, "operationName"); - var graphQLRequest2 = new GraphQLRequest("{hero{name}}", new { varName = "varValue" }, "operationName"); - Assert.True(graphQLRequest1.GetHashCode() == graphQLRequest2.GetHashCode()); - } + [Fact] + public void InEqualityOperatorFact() + { + var graphQLRequest1 = new GraphQLRequest("{hero{name1}}"); + var graphQLRequest2 = new GraphQLRequest("{hero{name2}}"); + Assert.True(graphQLRequest1 != graphQLRequest2); + } - [Fact] - public void GetHashCode3Fact() - { - var graphQLRequest1 = new GraphQLRequest("{hero{name}}", new { varName = "varValue1" }, "operationName"); - var graphQLRequest2 = new GraphQLRequest("{hero{name}}", new { varName = "varValue2" }, "operationName"); - Assert.True(graphQLRequest1.GetHashCode() != graphQLRequest2.GetHashCode()); - } + [Fact] + public void GetHashCode1Fact() + { + var graphQLRequest1 = new GraphQLRequest("{hero{name}}"); + var graphQLRequest2 = new GraphQLRequest("{hero{name}}"); + Assert.True(graphQLRequest1.GetHashCode() == graphQLRequest2.GetHashCode()); + } - [Fact] - public void PropertyQueryGetFact() - { - var graphQLRequest = new GraphQLRequest("{hero{name}}", new { varName = "varValue1" }, "operationName"); - Assert.Equal("{hero{name}}", graphQLRequest.Query); - } + [Fact] + public void GetHashCode2Fact() + { + var graphQLRequest1 = new GraphQLRequest("{hero{name}}", new { varName = "varValue" }, "operationName"); + var graphQLRequest2 = new GraphQLRequest("{hero{name}}", new { varName = "varValue" }, "operationName"); + Assert.True(graphQLRequest1.GetHashCode() == graphQLRequest2.GetHashCode()); + } - [Fact] - public void PropertyQuerySetFact() - { - var graphQLRequest = new GraphQLRequest("{hero{name}}", new { varName = "varValue1" }, "operationName") - { - Query = "{hero{name2}}" - }; - Assert.Equal("{hero{name2}}", graphQLRequest.Query); - } + [Fact] + public void GetHashCode3Fact() + { + var graphQLRequest1 = new GraphQLRequest("{hero{name}}", new { varName = "varValue1" }, "operationName"); + var graphQLRequest2 = new GraphQLRequest("{hero{name}}", new { varName = "varValue2" }, "operationName"); + Assert.True(graphQLRequest1.GetHashCode() != graphQLRequest2.GetHashCode()); + } - [Fact] - public void PropertyOperationNameGetFact() - { - var graphQLRequest = new GraphQLRequest("{hero{name}}", new { varName = "varValue" }, "operationName"); - Assert.Equal("operationName", graphQLRequest.OperationName); - } + [Fact] + public void PropertyQueryGetFact() + { + var graphQLRequest = new GraphQLRequest("{hero{name}}", new { varName = "varValue1" }, "operationName"); + Assert.Equal("{hero{name}}", graphQLRequest.Query); + } - [Fact] - public void PropertyOperationNameNullGetFact() + [Fact] + public void PropertyQuerySetFact() + { + var graphQLRequest = new GraphQLRequest("{hero{name}}", new { varName = "varValue1" }, "operationName") { - var graphQLRequest = new GraphQLRequest("{hero{name}}"); - Assert.Null(graphQLRequest.OperationName); - } + Query = "{hero{name2}}" + }; + Assert.Equal("{hero{name2}}", graphQLRequest.Query); + } - [Fact] - public void PropertyOperationNameSetFact() - { - var graphQLRequest = new GraphQLRequest("{hero{name}}", new { varName = "varValue" }, "operationName1") - { - OperationName = "operationName2" - }; - Assert.Equal("operationName2", graphQLRequest.OperationName); - } + [Fact] + public void PropertyOperationNameGetFact() + { + var graphQLRequest = new GraphQLRequest("{hero{name}}", new { varName = "varValue" }, "operationName"); + Assert.Equal("operationName", graphQLRequest.OperationName); + } - [Fact] - public void PropertyVariableGetFact() - { - var graphQLRequest = new GraphQLRequest("{hero{name}}", new { varName = "varValue" }, "operationName"); - Assert.Equal(new { varName = "varValue" }, graphQLRequest.Variables); - } + [Fact] + public void PropertyOperationNameNullGetFact() + { + var graphQLRequest = new GraphQLRequest("{hero{name}}"); + Assert.Null(graphQLRequest.OperationName); + } - [Fact] - public void PropertyVariableNullGetFact() + [Fact] + public void PropertyOperationNameSetFact() + { + var graphQLRequest = new GraphQLRequest("{hero{name}}", new { varName = "varValue" }, "operationName1") { - var graphQLRequest = new GraphQLRequest("{hero{name}}"); - Assert.Null(graphQLRequest.Variables); - } + OperationName = "operationName2" + }; + Assert.Equal("operationName2", graphQLRequest.OperationName); + } + + [Fact] + public void PropertyVariableGetFact() + { + var graphQLRequest = new GraphQLRequest("{hero{name}}", new { varName = "varValue" }, "operationName"); + Assert.Equal(new { varName = "varValue" }, graphQLRequest.Variables); + } + + [Fact] + public void PropertyVariableNullGetFact() + { + var graphQLRequest = new GraphQLRequest("{hero{name}}"); + Assert.Null(graphQLRequest.Variables); + } - [Fact] - public void PropertyVariableSetFact() + [Fact] + public void PropertyVariableSetFact() + { + var graphQLRequest = new GraphQLRequest("{hero{name}}", new { varName = "varValue1" }, "operationName1") { - var graphQLRequest = new GraphQLRequest("{hero{name}}", new { varName = "varValue1" }, "operationName1") - { - Variables = new - { - varName = "varValue2" - } - }; - Assert.Equal(new + Variables = new { varName = "varValue2" - }, graphQLRequest.Variables); - } + } + }; + Assert.Equal(new + { + varName = "varValue2" + }, graphQLRequest.Variables); } } diff --git a/tests/GraphQL.Primitives.Tests/GraphQLResponseTest.cs b/tests/GraphQL.Primitives.Tests/GraphQLResponseTest.cs index ce062e05..6db1dcbf 100644 --- a/tests/GraphQL.Primitives.Tests/GraphQLResponseTest.cs +++ b/tests/GraphQL.Primitives.Tests/GraphQLResponseTest.cs @@ -1,114 +1,113 @@ using Xunit; -namespace GraphQL.Primitives.Tests +namespace GraphQL.Primitives.Tests; + +public class GraphQLResponseTest { - public class GraphQLResponseTest + [Fact] + public void Constructor1Fact() { - [Fact] - public void Constructor1Fact() - { - var graphQLResponse = new GraphQLResponse(); - Assert.Null(graphQLResponse.Data); - Assert.Null(graphQLResponse.Errors); - } + var graphQLResponse = new GraphQLResponse(); + Assert.Null(graphQLResponse.Data); + Assert.Null(graphQLResponse.Errors); + } - [Fact] - public void Constructor2Fact() + [Fact] + public void Constructor2Fact() + { + var graphQLResponse = new GraphQLResponse { - var graphQLResponse = new GraphQLResponse - { - Data = new { a = 1 }, - Errors = new[] { new GraphQLError { Message = "message" } } - }; - Assert.NotNull(graphQLResponse.Data); - Assert.NotNull(graphQLResponse.Errors); - } + Data = new { a = 1 }, + Errors = new[] { new GraphQLError { Message = "message" } } + }; + Assert.NotNull(graphQLResponse.Data); + Assert.NotNull(graphQLResponse.Errors); + } - [Fact] - public void Equality1Fact() - { - var graphQLResponse = new GraphQLResponse(); - Assert.Equal(graphQLResponse, graphQLResponse); - } + [Fact] + public void Equality1Fact() + { + var graphQLResponse = new GraphQLResponse(); + Assert.Equal(graphQLResponse, graphQLResponse); + } - [Fact] - public void Equality2Fact() - { - var graphQLResponse1 = new GraphQLResponse(); - var graphQLResponse2 = new GraphQLResponse(); - Assert.Equal(graphQLResponse1, graphQLResponse2); - } + [Fact] + public void Equality2Fact() + { + var graphQLResponse1 = new GraphQLResponse(); + var graphQLResponse2 = new GraphQLResponse(); + Assert.Equal(graphQLResponse1, graphQLResponse2); + } - [Fact] - public void Equality3Fact() + [Fact] + public void Equality3Fact() + { + var graphQLResponse1 = new GraphQLResponse { - var graphQLResponse1 = new GraphQLResponse - { - Data = new { a = 1 }, - Errors = new[] { new GraphQLError { Message = "message" } } - }; - var graphQLResponse2 = new GraphQLResponse - { - Data = new { a = 1 }, - Errors = new[] { new GraphQLError { Message = "message" } } - }; - Assert.Equal(graphQLResponse1, graphQLResponse2); - } - - [Fact] - public void EqualityOperatorFact() + Data = new { a = 1 }, + Errors = new[] { new GraphQLError { Message = "message" } } + }; + var graphQLResponse2 = new GraphQLResponse { - var graphQLResponse1 = new GraphQLResponse(); - var graphQLResponse2 = new GraphQLResponse(); - Assert.True(graphQLResponse1 == graphQLResponse2); - } + Data = new { a = 1 }, + Errors = new[] { new GraphQLError { Message = "message" } } + }; + Assert.Equal(graphQLResponse1, graphQLResponse2); + } + + [Fact] + public void EqualityOperatorFact() + { + var graphQLResponse1 = new GraphQLResponse(); + var graphQLResponse2 = new GraphQLResponse(); + Assert.True(graphQLResponse1 == graphQLResponse2); + } - [Fact] - public void InEqualityFact() + [Fact] + public void InEqualityFact() + { + var graphQLResponse1 = new GraphQLResponse + { + Data = new { a = 1 }, + Errors = new[] { new GraphQLError { Message = "message" } } + }; + var graphQLResponse2 = new GraphQLResponse { - var graphQLResponse1 = new GraphQLResponse - { - Data = new { a = 1 }, - Errors = new[] { new GraphQLError { Message = "message" } } - }; - var graphQLResponse2 = new GraphQLResponse - { - Data = new { a = 2 }, - Errors = new[] { new GraphQLError { Message = "message" } } - }; - Assert.NotEqual(graphQLResponse1, graphQLResponse2); - } + Data = new { a = 2 }, + Errors = new[] { new GraphQLError { Message = "message" } } + }; + Assert.NotEqual(graphQLResponse1, graphQLResponse2); + } - [Fact] - public void InEqualityOperatorFact() + [Fact] + public void InEqualityOperatorFact() + { + var graphQLResponse1 = new GraphQLResponse + { + Data = new { a = 1 }, + Errors = new[] { new GraphQLError { Message = "message" } } + }; + var graphQLResponse2 = new GraphQLResponse { - var graphQLResponse1 = new GraphQLResponse - { - Data = new { a = 1 }, - Errors = new[] { new GraphQLError { Message = "message" } } - }; - var graphQLResponse2 = new GraphQLResponse - { - Data = new { a = 2 }, - Errors = new[] { new GraphQLError { Message = "message" } } - }; - Assert.True(graphQLResponse1 != graphQLResponse2); - } + Data = new { a = 2 }, + Errors = new[] { new GraphQLError { Message = "message" } } + }; + Assert.True(graphQLResponse1 != graphQLResponse2); + } - [Fact] - public void GetHashCodeFact() + [Fact] + public void GetHashCodeFact() + { + var graphQLResponse1 = new GraphQLResponse + { + Data = new { a = 1 }, + Errors = new[] { new GraphQLError { Message = "message" } } + }; + var graphQLResponse2 = new GraphQLResponse { - var graphQLResponse1 = new GraphQLResponse - { - Data = new { a = 1 }, - Errors = new[] { new GraphQLError { Message = "message" } } - }; - var graphQLResponse2 = new GraphQLResponse - { - Data = new { a = 1 }, - Errors = new[] { new GraphQLError { Message = "message" } } - }; - Assert.True(graphQLResponse1.GetHashCode() == graphQLResponse2.GetHashCode()); - } + Data = new { a = 1 }, + Errors = new[] { new GraphQLError { Message = "message" } } + }; + Assert.True(graphQLResponse1.GetHashCode() == graphQLResponse2.GetHashCode()); } } diff --git a/tests/GraphQL.Primitives.Tests/JsonSerializationTests.cs b/tests/GraphQL.Primitives.Tests/JsonSerializationTests.cs index 74add0a1..2fb2bbc0 100644 --- a/tests/GraphQL.Primitives.Tests/JsonSerializationTests.cs +++ b/tests/GraphQL.Primitives.Tests/JsonSerializationTests.cs @@ -3,32 +3,31 @@ using FluentAssertions; using Xunit; -namespace GraphQL.Primitives.Tests +namespace GraphQL.Primitives.Tests; + +public class JsonSerializationTests { - public class JsonSerializationTests + [Fact] + public void WebSocketResponseDeserialization() { - [Fact] - public void WebSocketResponseDeserialization() - { - var testObject = new ExtendedTestObject { Id = "test", OtherData = "this is some other stuff" }; - var json = JsonSerializer.Serialize(testObject); - var deserialized = JsonSerializer.Deserialize(json); - deserialized.Id.Should().Be("test"); - var dict = JsonSerializer.Deserialize>(json); - var childObject = (JsonElement)dict["ChildObject"]; - childObject.GetProperty("Id").GetString().Should().Be(testObject.ChildObject.Id); - } + var testObject = new ExtendedTestObject { Id = "test", OtherData = "this is some other stuff" }; + var json = JsonSerializer.Serialize(testObject); + var deserialized = JsonSerializer.Deserialize(json); + deserialized.Id.Should().Be("test"); + var dict = JsonSerializer.Deserialize>(json); + var childObject = (JsonElement)dict["ChildObject"]; + childObject.GetProperty("Id").GetString().Should().Be(testObject.ChildObject.Id); + } - public class TestObject - { - public string Id { get; set; } - } + public class TestObject + { + public string Id { get; set; } + } - public class ExtendedTestObject : TestObject - { - public string OtherData { get; set; } + public class ExtendedTestObject : TestObject + { + public string OtherData { get; set; } - public TestObject ChildObject { get; set; } = new TestObject { Id = "1337" }; - } + public TestObject ChildObject { get; set; } = new TestObject { Id = "1337" }; } } diff --git a/tests/GraphQL.Server.Test/GraphQL/Models/Repository.cs b/tests/GraphQL.Server.Test/GraphQL/Models/Repository.cs index b262a6bd..7fcfc500 100644 --- a/tests/GraphQL.Server.Test/GraphQL/Models/Repository.cs +++ b/tests/GraphQL.Server.Test/GraphQL/Models/Repository.cs @@ -1,30 +1,29 @@ using GraphQL.Types; -namespace GraphQL.Server.Test.GraphQL.Models +namespace GraphQL.Server.Test.GraphQL.Models; + +public class Repository { - public class Repository - { - public int DatabaseId { get; set; } + public int DatabaseId { get; set; } - public string? Id { get; set; } + public string? Id { get; set; } - public string? Name { get; set; } + public string? Name { get; set; } - public object? Owner { get; set; } + public object? Owner { get; set; } - public Uri? Url { get; set; } - } + public Uri? Url { get; set; } +} - public class RepositoryGraphType : ObjectGraphType +public class RepositoryGraphType : ObjectGraphType +{ + public RepositoryGraphType() { - public RepositoryGraphType() - { - Name = nameof(Repository); - Field(expression => expression.DatabaseId); - Field>("id"); - Field(expression => expression.Name); - //this.Field(expression => expression.Owner); - Field>("url"); - } + Name = nameof(Repository); + Field(expression => expression.DatabaseId); + Field>("id"); + Field(expression => expression.Name); + //this.Field(expression => expression.Owner); + Field>("url"); } } diff --git a/tests/GraphQL.Server.Test/GraphQL/Storage.cs b/tests/GraphQL.Server.Test/GraphQL/Storage.cs index f157da14..cc8887e0 100644 --- a/tests/GraphQL.Server.Test/GraphQL/Storage.cs +++ b/tests/GraphQL.Server.Test/GraphQL/Storage.cs @@ -1,18 +1,17 @@ using GraphQL.Server.Test.GraphQL.Models; -namespace GraphQL.Server.Test.GraphQL +namespace GraphQL.Server.Test.GraphQL; + +public static class Storage { - public static class Storage - { - public static IQueryable Repositories { get; } = new List() - .Append(new Repository - { - DatabaseId = 113196300, - Id = "MDEwOlJlcG9zaXRvcnkxMTMxOTYzMDA=", - Name = "graphql-client", - Owner = null, - Url = new Uri("https://github.com/graphql-dotnet/graphql-client") - }) - .AsQueryable(); - } + public static IQueryable Repositories { get; } = new List() + .Append(new Repository + { + DatabaseId = 113196300, + Id = "MDEwOlJlcG9zaXRvcnkxMTMxOTYzMDA=", + Name = "graphql-client", + Owner = null, + Url = new Uri("https://github.com/graphql-dotnet/graphql-client") + }) + .AsQueryable(); } diff --git a/tests/GraphQL.Server.Test/GraphQL/TestMutation.cs b/tests/GraphQL.Server.Test/GraphQL/TestMutation.cs index dfd67981..ffae2332 100644 --- a/tests/GraphQL.Server.Test/GraphQL/TestMutation.cs +++ b/tests/GraphQL.Server.Test/GraphQL/TestMutation.cs @@ -1,11 +1,10 @@ using GraphQL.Types; -namespace GraphQL.Server.Test.GraphQL +namespace GraphQL.Server.Test.GraphQL; + +public class TestMutation : ObjectGraphType { - public class TestMutation : ObjectGraphType + public TestMutation() { - public TestMutation() - { - } } } diff --git a/tests/GraphQL.Server.Test/GraphQL/TestQuery.cs b/tests/GraphQL.Server.Test/GraphQL/TestQuery.cs index 4eb32723..31bbef6e 100644 --- a/tests/GraphQL.Server.Test/GraphQL/TestQuery.cs +++ b/tests/GraphQL.Server.Test/GraphQL/TestQuery.cs @@ -1,18 +1,17 @@ using GraphQL.Server.Test.GraphQL.Models; using GraphQL.Types; -namespace GraphQL.Server.Test.GraphQL +namespace GraphQL.Server.Test.GraphQL; + +public class TestQuery : ObjectGraphType { - public class TestQuery : ObjectGraphType + public TestQuery() { - public TestQuery() + Field("repository", arguments: new QueryArguments(new QueryArgument> { Name = "owner" }, new QueryArgument> { Name = "name" }), resolve: context => { - Field("repository", arguments: new QueryArguments(new QueryArgument> { Name = "owner" }, new QueryArgument> { Name = "name" }), resolve: context => - { - var owner = context.GetArgument("owner"); - var name = context.GetArgument("name"); - return Storage.Repositories.FirstOrDefault(predicate => predicate.Name == name); - }); - } + var owner = context.GetArgument("owner"); + var name = context.GetArgument("name"); + return Storage.Repositories.FirstOrDefault(predicate => predicate.Name == name); + }); } } diff --git a/tests/GraphQL.Server.Test/GraphQL/TestSchema.cs b/tests/GraphQL.Server.Test/GraphQL/TestSchema.cs index c589d388..feecf79e 100644 --- a/tests/GraphQL.Server.Test/GraphQL/TestSchema.cs +++ b/tests/GraphQL.Server.Test/GraphQL/TestSchema.cs @@ -1,14 +1,13 @@ using GraphQL.Types; -namespace GraphQL.Server.Test.GraphQL +namespace GraphQL.Server.Test.GraphQL; + +public class TestSchema : Schema { - public class TestSchema : Schema + public TestSchema() { - public TestSchema() - { - Query = new TestQuery(); - //this.Mutation = new TestMutation(); - //this.Subscription = new TestSubscription(); - } + Query = new TestQuery(); + //this.Mutation = new TestMutation(); + //this.Subscription = new TestSubscription(); } } diff --git a/tests/GraphQL.Server.Test/GraphQL/TestSubscription.cs b/tests/GraphQL.Server.Test/GraphQL/TestSubscription.cs index 4fabf386..d8ebc5de 100644 --- a/tests/GraphQL.Server.Test/GraphQL/TestSubscription.cs +++ b/tests/GraphQL.Server.Test/GraphQL/TestSubscription.cs @@ -1,11 +1,10 @@ using GraphQL.Types; -namespace GraphQL.Server.Test.GraphQL +namespace GraphQL.Server.Test.GraphQL; + +public class TestSubscription : ObjectGraphType { - public class TestSubscription : ObjectGraphType + public TestSubscription() { - public TestSubscription() - { - } } } diff --git a/tests/GraphQL.Server.Test/Program.cs b/tests/GraphQL.Server.Test/Program.cs index 98538f1f..f205fd5c 100644 --- a/tests/GraphQL.Server.Test/Program.cs +++ b/tests/GraphQL.Server.Test/Program.cs @@ -1,15 +1,14 @@ using Microsoft.AspNetCore; -namespace GraphQL.Server.Test +namespace GraphQL.Server.Test; + +public static class Program { - public static class Program - { - public static async Task Main(string[] args) => - await CreateHostBuilder(args).Build().RunAsync(); + public static async Task Main(string[] args) => + await CreateHostBuilder(args).Build().RunAsync(); - public static IWebHostBuilder CreateHostBuilder(string[] args = null) => - WebHost.CreateDefaultBuilder(args) - .UseKestrel(options => options.AllowSynchronousIO = true) - .UseStartup(); - } + public static IWebHostBuilder CreateHostBuilder(string[] args = null) => + WebHost.CreateDefaultBuilder(args) + .UseKestrel(options => options.AllowSynchronousIO = true) + .UseStartup(); } diff --git a/tests/GraphQL.Server.Test/Startup.cs b/tests/GraphQL.Server.Test/Startup.cs index 2421cf70..25e1e9d5 100644 --- a/tests/GraphQL.Server.Test/Startup.cs +++ b/tests/GraphQL.Server.Test/Startup.cs @@ -2,33 +2,32 @@ using GraphQL.Server.Test.GraphQL; using GraphQL.Server.Ui.GraphiQL; -namespace GraphQL.Server.Test +namespace GraphQL.Server.Test; + +public class Startup { - public class Startup + public void Configure(IApplicationBuilder app) { - public void Configure(IApplicationBuilder app) + var webHostEnvironment = app.ApplicationServices.GetRequiredService(); + if (webHostEnvironment.IsDevelopment()) { - var webHostEnvironment = app.ApplicationServices.GetRequiredService(); - if (webHostEnvironment.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - app.UseHttpsRedirection(); - - app.UseWebSockets(); - app.UseGraphQLWebSockets(); - app.UseGraphQL(); - app.UseGraphQLGraphiQL(new GraphiQLOptions { }); + app.UseDeveloperExceptionPage(); } + app.UseHttpsRedirection(); - public void ConfigureServices(IServiceCollection services) - { - services.AddGraphQL(builder => builder - .AddSchema() - .AddApolloTracing(enableMetrics: true) - .AddErrorInfoProvider(opt => opt.ExposeExceptionStackTrace = true) - .AddWebSockets() - ); - } + app.UseWebSockets(); + app.UseGraphQLWebSockets(); + app.UseGraphQL(); + app.UseGraphQLGraphiQL(new GraphiQLOptions { }); + } + + public void ConfigureServices(IServiceCollection services) + { + services.AddGraphQL(builder => builder + .AddSchema() + .AddApolloTracing(enableMetrics: true) + .AddErrorInfoProvider(opt => opt.ExposeExceptionStackTrace = true) + .AddWebSockets() + ); } } diff --git a/tests/IntegrationTestServer/Program.cs b/tests/IntegrationTestServer/Program.cs index ac56ea87..3220c40d 100644 --- a/tests/IntegrationTestServer/Program.cs +++ b/tests/IntegrationTestServer/Program.cs @@ -1,14 +1,13 @@ using Microsoft.AspNetCore; -namespace IntegrationTestServer +namespace IntegrationTestServer; + +public static class Program { - public static class Program - { - public static void Main(string[] args) => CreateWebHostBuilder(args).Build().Run(); + public static void Main(string[] args) => CreateWebHostBuilder(args).Build().Run(); - public static IWebHostBuilder CreateWebHostBuilder(string[] args) => - WebHost.CreateDefaultBuilder(args) - .UseStartup() - .ConfigureLogging((_, logging) => logging.SetMinimumLevel(LogLevel.Debug)); - } + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup() + .ConfigureLogging((_, logging) => logging.SetMinimumLevel(LogLevel.Debug)); } diff --git a/tests/IntegrationTestServer/Startup.cs b/tests/IntegrationTestServer/Startup.cs index c8441472..4876fdb3 100644 --- a/tests/IntegrationTestServer/Startup.cs +++ b/tests/IntegrationTestServer/Startup.cs @@ -10,67 +10,66 @@ using GraphQL.Types; using Microsoft.AspNetCore.Server.Kestrel.Core; -namespace IntegrationTestServer +namespace IntegrationTestServer; + +public class Startup { - public class Startup + public Startup(IConfiguration configuration, IWebHostEnvironment environment) { - public Startup(IConfiguration configuration, IWebHostEnvironment environment) - { - Configuration = configuration; - Environment = environment; - } + Configuration = configuration; + Environment = environment; + } - public IConfiguration Configuration { get; } + public IConfiguration Configuration { get; } - public IWebHostEnvironment Environment { get; } + public IWebHostEnvironment Environment { get; } - // This method gets called by the runtime. Use this method to add services to the container. - // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 - public void ConfigureServices(IServiceCollection services) - { - services.Configure(options => options.AllowSynchronousIO = true); - // - services.AddChatSchema(); - services.AddStarWarsSchema(); - services.AddGraphQL(builder => builder - .AddApolloTracing(enableMetrics: true) - .AddHttpMiddleware() - .AddHttpMiddleware() - .AddWebSocketsHttpMiddleware() - .AddWebSocketsHttpMiddleware() - .ConfigureExecutionOptions(opt => opt.UnhandledExceptionDelegate = ctx => - { - var logger = ctx.Context.RequestServices.GetRequiredService>(); - logger.LogError("{Error} occurred", ctx.OriginalException.Message); - return System.Threading.Tasks.Task.CompletedTask; - }) - .AddErrorInfoProvider(opt => opt.ExposeExceptionStackTrace = Environment.IsDevelopment()) - .AddSystemTextJson() - .AddWebSockets() - .AddGraphTypes(typeof(ChatSchema).Assembly)); - } + // This method gets called by the runtime. Use this method to add services to the container. + // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 + public void ConfigureServices(IServiceCollection services) + { + services.Configure(options => options.AllowSynchronousIO = true); + // + services.AddChatSchema(); + services.AddStarWarsSchema(); + services.AddGraphQL(builder => builder + .AddApolloTracing(enableMetrics: true) + .AddHttpMiddleware() + .AddHttpMiddleware() + .AddWebSocketsHttpMiddleware() + .AddWebSocketsHttpMiddleware() + .ConfigureExecutionOptions(opt => opt.UnhandledExceptionDelegate = ctx => + { + var logger = ctx.Context.RequestServices.GetRequiredService>(); + logger.LogError("{Error} occurred", ctx.OriginalException.Message); + return System.Threading.Tasks.Task.CompletedTask; + }) + .AddErrorInfoProvider(opt => opt.ExposeExceptionStackTrace = Environment.IsDevelopment()) + .AddSystemTextJson() + .AddWebSockets() + .AddGraphTypes(typeof(ChatSchema).Assembly)); + } - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } + app.UseDeveloperExceptionPage(); + } - app.UseWebSockets(); + app.UseWebSockets(); - ConfigureGraphQLSchema(app, Common.CHAT_ENDPOINT); - ConfigureGraphQLSchema(app, Common.STAR_WARS_ENDPOINT); + ConfigureGraphQLSchema(app, Common.CHAT_ENDPOINT); + ConfigureGraphQLSchema(app, Common.STAR_WARS_ENDPOINT); - app.UseGraphQLGraphiQL(new GraphiQLOptions { GraphQLEndPoint = Common.STAR_WARS_ENDPOINT }); - app.UseGraphQLAltair(new AltairOptions { GraphQLEndPoint = Common.CHAT_ENDPOINT }); - } + app.UseGraphQLGraphiQL(new GraphiQLOptions { GraphQLEndPoint = Common.STAR_WARS_ENDPOINT }); + app.UseGraphQLAltair(new AltairOptions { GraphQLEndPoint = Common.CHAT_ENDPOINT }); + } - private void ConfigureGraphQLSchema(IApplicationBuilder app, string endpoint) where TSchema : Schema - { - app.UseGraphQLWebSockets(endpoint); - app.UseGraphQL(endpoint); - } + private void ConfigureGraphQLSchema(IApplicationBuilder app, string endpoint) where TSchema : Schema + { + app.UseGraphQLWebSockets(endpoint); + app.UseGraphQL(endpoint); } } From b4c6c82892cab3c6521fb9b470f8e67448413ade Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Tue, 2 Aug 2022 00:06:39 +0300 Subject: [PATCH 389/455] Fix default behavior for 401/403 http response codes (#429) * Fix default behavior for 401/403 http response codes * Add Accept and Accept-Charset headers * fix default user agent version * add note --- src/GraphQL.Client/GraphQLHttpClientOptions.cs | 6 +++++- src/GraphQL.Client/GraphQLHttpRequest.cs | 4 ++++ .../QueryAndMutationTests/Base.cs | 9 +++++++-- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/GraphQL.Client/GraphQLHttpClientOptions.cs b/src/GraphQL.Client/GraphQLHttpClientOptions.cs index 12fad5e3..549760c2 100644 --- a/src/GraphQL.Client/GraphQLHttpClientOptions.cs +++ b/src/GraphQL.Client/GraphQLHttpClientOptions.cs @@ -52,8 +52,12 @@ public class GraphQLHttpClientOptions /// /// Delegate to determine if GraphQL response may be properly deserialized into . + /// Note that compatible to the draft graphql-over-http spec GraphQL Server MAY return 4xx status codes (401/403, etc.) + /// with well-formed GraphQL response containing errors collection. /// - public Func IsValidResponseToDeserialize { get; set; } = r => r.IsSuccessStatusCode || r.StatusCode == HttpStatusCode.BadRequest; + public Func IsValidResponseToDeserialize { get; set; } = r => + // Why not application/json? See https://github.com/graphql/graphql-over-http/blob/main/spec/GraphQLOverHTTP.md#processing-the-response + r.IsSuccessStatusCode || r.StatusCode == HttpStatusCode.BadRequest || r.Content.Headers.ContentType.MediaType == "application/graphql+json"; /// /// This callback is called after successfully establishing a websocket connection but before any regular request is made. diff --git a/src/GraphQL.Client/GraphQLHttpRequest.cs b/src/GraphQL.Client/GraphQLHttpRequest.cs index b0591e57..2da60705 100644 --- a/src/GraphQL.Client/GraphQLHttpRequest.cs +++ b/src/GraphQL.Client/GraphQLHttpRequest.cs @@ -1,3 +1,4 @@ +using System.Net.Http.Headers; using System.Runtime.Serialization; using System.Text; using GraphQL.Client.Abstractions; @@ -38,6 +39,9 @@ public virtual HttpRequestMessage ToHttpRequestMessage(GraphQLHttpClientOptions { Content = new StringContent(serializer.SerializeToString(this), Encoding.UTF8, options.MediaType) }; + message.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/graphql+json")); + message.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + message.Headers.AcceptCharset.Add(new StringWithQualityHeaderValue("utf-8")); #pragma warning disable CS0618 // Type or member is obsolete PreprocessHttpRequestMessage(message); diff --git a/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs b/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs index 600ca463..abcdc644 100644 --- a/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs +++ b/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs @@ -1,4 +1,5 @@ using System.Net; +using System.Net.Http.Headers; using FluentAssertions; using GraphQL.Client.Abstractions; using GraphQL.Client.Http; @@ -173,9 +174,13 @@ public async void PreprocessHttpRequestMessageIsCalled() #pragma warning restore CS0618 // Type or member is obsolete }; - var defaultHeaders = StarWarsClient.HttpClient.DefaultRequestHeaders; + var expectedHeaders = new HttpRequestMessage().Headers; + expectedHeaders.UserAgent.Add(new ProductInfoHeaderValue("GraphQL.Client", typeof(GraphQLHttpClient).Assembly.GetName().Version.ToString())); + expectedHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/graphql+json")); + expectedHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + expectedHeaders.AcceptCharset.Add(new StringWithQualityHeaderValue("utf-8")); var response = await StarWarsClient.SendQueryAsync(graphQLRequest, () => new { Human = new { Name = string.Empty } }); - callbackTester.Should().HaveBeenInvokedWithPayload().Which.Headers.Should().BeEquivalentTo(defaultHeaders); + callbackTester.Should().HaveBeenInvokedWithPayload().Which.Headers.Should().BeEquivalentTo(expectedHeaders); Assert.Null(response.Errors); Assert.Equal("Luke", response.Data.Human.Name); } From 8379f8f2fc66326a87a397cc2fee294ff42ece6a Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Tue, 2 Aug 2022 00:12:33 +0300 Subject: [PATCH 390/455] Fix ArgumentNullException from ClientWebSocketOptions.ClientCertificates (#431) --- src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs index 28b721eb..1afa6b98 100644 --- a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs +++ b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs @@ -399,7 +399,9 @@ public Task InitializeWebSocket() // the following properties are not supported in Blazor WebAssembly and throw a PlatformNotSupportedException error when accessed try { - _clientWebSocket.Options.ClientCertificates = ((HttpClientHandler)Options.HttpMessageHandler).ClientCertificates; + var certs = ((HttpClientHandler)Options.HttpMessageHandler).ClientCertificates; + if (certs != null) // ClientWebSocketOptions.ClientCertificates.set throws ArgumentNullException + _clientWebSocket.Options.ClientCertificates = certs; } catch (NotImplementedException) { From de502926b5db8cbd0f7f9119c9237b5ec573fb03 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Tue, 2 Aug 2022 11:00:02 +0200 Subject: [PATCH 391/455] Fix NullReferenceException from GraphQLHttpClientOptions.IsValidResponseToDeserialize (#433) Avoid NullReferenceException when Headers.ContentType is null --- src/GraphQL.Client/GraphQLHttpClientOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GraphQL.Client/GraphQLHttpClientOptions.cs b/src/GraphQL.Client/GraphQLHttpClientOptions.cs index 549760c2..22b5ddb2 100644 --- a/src/GraphQL.Client/GraphQLHttpClientOptions.cs +++ b/src/GraphQL.Client/GraphQLHttpClientOptions.cs @@ -57,7 +57,7 @@ public class GraphQLHttpClientOptions /// public Func IsValidResponseToDeserialize { get; set; } = r => // Why not application/json? See https://github.com/graphql/graphql-over-http/blob/main/spec/GraphQLOverHTTP.md#processing-the-response - r.IsSuccessStatusCode || r.StatusCode == HttpStatusCode.BadRequest || r.Content.Headers.ContentType.MediaType == "application/graphql+json"; + r.IsSuccessStatusCode || r.StatusCode == HttpStatusCode.BadRequest || r.Content.Headers.ContentType?.MediaType == "application/graphql+json"; /// /// This callback is called after successfully establishing a websocket connection but before any regular request is made. From 62dd4e9011f0eec55a36e810159ad0b449874bf9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Aug 2022 12:09:01 +0300 Subject: [PATCH 392/455] Bump xunit from 2.4.1 to 2.4.2 (#434) Bumps [xunit](https://github.com/xunit/xunit) from 2.4.1 to 2.4.2. - [Release notes](https://github.com/xunit/xunit/releases) - [Commits](https://github.com/xunit/xunit/compare/2.4.1...2.4.2) --- updated-dependencies: - dependency-name: xunit dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tests/tests.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tests.props b/tests/tests.props index 28e5e65c..10dc01bc 100644 --- a/tests/tests.props +++ b/tests/tests.props @@ -8,7 +8,7 @@ - + all From 9c91069a19bf88819e2049e22596253aa46ccf5c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Aug 2022 01:30:23 +0300 Subject: [PATCH 393/455] Bump Microsoft.AspNetCore.Mvc.Testing from 6.0.7 to 6.0.8 (#436) Bumps [Microsoft.AspNetCore.Mvc.Testing](https://github.com/dotnet/aspnetcore) from 6.0.7 to 6.0.8. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md) - [Commits](https://github.com/dotnet/aspnetcore/compare/v6.0.7...v6.0.8) --- updated-dependencies: - dependency-name: Microsoft.AspNetCore.Mvc.Testing dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj b/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj index 1377f01a..46a82161 100644 --- a/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj +++ b/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj @@ -7,7 +7,7 @@ - + From 9da003ca3f61a13d99bd1adfe14e37f611af3728 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Aug 2022 01:31:39 +0300 Subject: [PATCH 394/455] Bump Microsoft.NET.Test.Sdk from 17.2.0 to 17.3.0 (#437) Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.2.0 to 17.3.0. - [Release notes](https://github.com/microsoft/vstest/releases) - [Commits](https://github.com/microsoft/vstest/compare/v17.2.0...v17.3.0) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tests/tests.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tests.props b/tests/tests.props index 10dc01bc..d0969963 100644 --- a/tests/tests.props +++ b/tests/tests.props @@ -7,7 +7,7 @@ - + From b8892de01e3dad7b44c4b3f5120b8fa2b89df945 Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Mon, 22 Aug 2022 08:44:31 +0300 Subject: [PATCH 395/455] Migrate to GraphQL.NET v7 (#447) --- .../GraphQL.Client.LocalExecution.csproj | 4 ++-- .../GraphQLLocalExecutionClient.cs | 2 +- src/GraphQL.Primitives/GraphQLError.cs | 2 +- .../BaseSerializeNoCamelCaseTest.cs | 1 - .../GraphQL.Client.Serializer.Tests.csproj | 4 ++-- .../NewtonsoftSerializerTest.cs | 13 ++++++++++-- .../SystemTextJsonSerializerTests.cs | 13 ++++++++++-- .../Chat/Schema/CapitalizedFieldsGraphType.cs | 3 +-- .../Chat/Schema/ChatMutation.cs | 16 ++++++-------- .../Chat/Schema/ChatQuery.cs | 11 +++++----- .../Chat/Schema/ChatSubscriptions.cs | 8 ++----- .../GraphQL.Client.Tests.Common.csproj | 2 +- .../StarWars/StarWarsMutation.cs | 9 +++----- .../StarWars/StarWarsQuery.cs | 21 +++++++------------ .../StarWars/Types/CharacterInterface.cs | 6 +++--- .../StarWars/Types/DroidType.cs | 10 ++++----- .../StarWars/Types/HumanType.cs | 10 ++++----- .../Properties/launchSettings.json | 16 +++++++------- .../GraphQL.Server.Test.csproj | 7 +++---- .../GraphQL.Server.Test/GraphQL/TestQuery.cs | 5 ++++- tests/GraphQL.Server.Test/Startup.cs | 8 +++---- .../IntegrationTestServer.csproj | 2 +- tests/IntegrationTestServer/Startup.cs | 14 ++++--------- 23 files changed, 89 insertions(+), 98 deletions(-) diff --git a/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj b/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj index 6973829f..253f9283 100644 --- a/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj +++ b/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj @@ -1,4 +1,4 @@ - + A GraphQL Client which executes the queries directly on a provided GraphQL schema using graphql-dotnet @@ -6,7 +6,7 @@ - + diff --git a/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs b/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs index 15e4c3e3..0bd3a270 100644 --- a/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs +++ b/src/GraphQL.Client.LocalExecution/GraphQLLocalExecutionClient.cs @@ -9,7 +9,7 @@ public static class GraphQLLocalExecutionClient { public static GraphQLLocalExecutionClient New(TSchema schema, IGraphQLJsonSerializer clientSerializer, IGraphQLTextSerializer serverSerializer) where TSchema : ISchema - => new GraphQLLocalExecutionClient(schema, new DocumentExecuter(), clientSerializer, serverSerializer); + => new(schema, new DocumentExecuter(), clientSerializer, serverSerializer); } public class GraphQLLocalExecutionClient : IGraphQLClient where TSchema : ISchema diff --git a/src/GraphQL.Primitives/GraphQLError.cs b/src/GraphQL.Primitives/GraphQLError.cs index 29a94250..9732c4b6 100644 --- a/src/GraphQL.Primitives/GraphQLError.cs +++ b/src/GraphQL.Primitives/GraphQLError.cs @@ -27,7 +27,7 @@ public class GraphQLError : IEquatable /// /// The extensions of the error - /// + /// [DataMember(Name = "extensions")] public Map? Extensions { get; set; } diff --git a/tests/GraphQL.Client.Serializer.Tests/BaseSerializeNoCamelCaseTest.cs b/tests/GraphQL.Client.Serializer.Tests/BaseSerializeNoCamelCaseTest.cs index 9d97fea3..9dcabfd7 100644 --- a/tests/GraphQL.Client.Serializer.Tests/BaseSerializeNoCamelCaseTest.cs +++ b/tests/GraphQL.Client.Serializer.Tests/BaseSerializeNoCamelCaseTest.cs @@ -48,7 +48,6 @@ public void SerializeToBytesTest(string expectedJson, GraphQLWebSocketRequest re [Fact] public async void WorksWithoutCamelCaseNamingStrategy() { - const string message = "some random testing message"; var graphQLRequest = new GraphQLRequest( @"mutation($input: MessageInputType){ diff --git a/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj b/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj index 2e15b077..346b7790 100644 --- a/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj +++ b/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj @@ -7,8 +7,8 @@ - - + + diff --git a/tests/GraphQL.Client.Serializer.Tests/NewtonsoftSerializerTest.cs b/tests/GraphQL.Client.Serializer.Tests/NewtonsoftSerializerTest.cs index 42e392c4..a359e372 100644 --- a/tests/GraphQL.Client.Serializer.Tests/NewtonsoftSerializerTest.cs +++ b/tests/GraphQL.Client.Serializer.Tests/NewtonsoftSerializerTest.cs @@ -1,4 +1,5 @@ using GraphQL.Client.Serializer.Newtonsoft; +using GraphQL.Execution; using Newtonsoft.Json; namespace GraphQL.Client.Serializer.Tests; @@ -6,11 +7,19 @@ namespace GraphQL.Client.Serializer.Tests; public class NewtonsoftSerializerTest : BaseSerializerTest { public NewtonsoftSerializerTest() - : base(new NewtonsoftJsonSerializer(), new NewtonsoftJson.GraphQLSerializer()) { } + : base( + new NewtonsoftJsonSerializer(), + new NewtonsoftJson.GraphQLSerializer(new ErrorInfoProvider(opt => opt.ExposeData = true))) + { + } } public class NewtonsoftSerializeNoCamelCaseTest : BaseSerializeNoCamelCaseTest { public NewtonsoftSerializeNoCamelCaseTest() - : base(new NewtonsoftJsonSerializer(new JsonSerializerSettings { Converters = { new ConstantCaseEnumConverter() } }), new NewtonsoftJson.GraphQLSerializer()) { } + : base( + new NewtonsoftJsonSerializer(new JsonSerializerSettings { Converters = { new ConstantCaseEnumConverter() } }), + new NewtonsoftJson.GraphQLSerializer(new ErrorInfoProvider(opt => opt.ExposeData = true))) + { + } } diff --git a/tests/GraphQL.Client.Serializer.Tests/SystemTextJsonSerializerTests.cs b/tests/GraphQL.Client.Serializer.Tests/SystemTextJsonSerializerTests.cs index bece29bd..38ebc2a1 100644 --- a/tests/GraphQL.Client.Serializer.Tests/SystemTextJsonSerializerTests.cs +++ b/tests/GraphQL.Client.Serializer.Tests/SystemTextJsonSerializerTests.cs @@ -1,17 +1,26 @@ using System.Text.Json; using System.Text.Json.Serialization; using GraphQL.Client.Serializer.SystemTextJson; +using GraphQL.Execution; namespace GraphQL.Client.Serializer.Tests; public class SystemTextJsonSerializerTests : BaseSerializerTest { public SystemTextJsonSerializerTests() - : base(new SystemTextJsonSerializer(), new GraphQL.SystemTextJson.GraphQLSerializer()) { } + : base( + new SystemTextJsonSerializer(), + new GraphQL.SystemTextJson.GraphQLSerializer(new ErrorInfoProvider(opt => opt.ExposeData = true))) + { + } } public class SystemTextJsonSerializeNoCamelCaseTest : BaseSerializeNoCamelCaseTest { public SystemTextJsonSerializeNoCamelCaseTest() - : base(new SystemTextJsonSerializer(new JsonSerializerOptions { Converters = { new JsonStringEnumConverter(new ConstantCaseJsonNamingPolicy(), false)}}.SetupImmutableConverter()), new GraphQL.SystemTextJson.GraphQLSerializer()) { } + : base( + new SystemTextJsonSerializer(new JsonSerializerOptions { Converters = { new JsonStringEnumConverter(new ConstantCaseJsonNamingPolicy(), false)}}.SetupImmutableConverter()), + new GraphQL.SystemTextJson.GraphQLSerializer(new ErrorInfoProvider(opt => opt.ExposeData = true))) + { + } } diff --git a/tests/GraphQL.Client.Tests.Common/Chat/Schema/CapitalizedFieldsGraphType.cs b/tests/GraphQL.Client.Tests.Common/Chat/Schema/CapitalizedFieldsGraphType.cs index a1f185fb..c10a77b6 100644 --- a/tests/GraphQL.Client.Tests.Common/Chat/Schema/CapitalizedFieldsGraphType.cs +++ b/tests/GraphQL.Client.Tests.Common/Chat/Schema/CapitalizedFieldsGraphType.cs @@ -8,8 +8,7 @@ public CapitalizedFieldsGraphType() { Name = "CapitalizedFields"; - Field() - .Name("StringField") + Field("StringField") .Resolve(context => "hello world"); } } diff --git a/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatMutation.cs b/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatMutation.cs index 788eaaba..70a2a863 100644 --- a/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatMutation.cs +++ b/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatMutation.cs @@ -6,22 +6,18 @@ public class ChatMutation : ObjectGraphType { public ChatMutation(IChat chat) { - Field("addMessage", - arguments: new QueryArguments( - new QueryArgument { Name = "message" } - ), - resolve: context => + Field("addMessage") + .Argument("message") + .Resolve(context => { var receivedMessage = context.GetArgument("message"); var message = chat.AddMessage(receivedMessage); return message; }); - Field("join", - arguments: new QueryArguments( - new QueryArgument { Name = "userId" } - ), - resolve: context => + Field("join") + .Argument("userId") + .Resolve(context => { var userId = context.GetArgument("userId"); var userJoined = chat.Join(userId); diff --git a/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatQuery.cs b/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatQuery.cs index 854a4a75..61bf6055 100644 --- a/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatQuery.cs +++ b/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatQuery.cs @@ -4,7 +4,8 @@ namespace GraphQL.Client.Tests.Common.Chat.Schema; public class ChatQuery : ObjectGraphType { - public static readonly Dictionary TestExtensions = new Dictionary { + public static readonly Dictionary TestExtensions = new() + { {"extension1", "hello world"}, {"another extension", 4711}, {"long", 19942590700} @@ -19,18 +20,16 @@ public ChatQuery(IChat chat) { Name = "ChatQuery"; - Field>("messages", resolve: context => chat.AllMessages.Take(100)); + Field>("messages").Resolve(context => chat.AllMessages.Take(100)); - Field() - .Name("extensionsTest") + Field("extensionsTest") .Resolve(context => { context.Errors.Add(new ExecutionError("this error contains extension fields", TestExtensions)); return null; }); - Field() - .Name("longRunning") + Field("longRunning") .Resolve(context => { WaitingOnQueryBlocker.Set(); diff --git a/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatSubscriptions.cs b/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatSubscriptions.cs index 3a4f62e1..0653205d 100644 --- a/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatSubscriptions.cs +++ b/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatSubscriptions.cs @@ -1,7 +1,5 @@ using System.Reactive.Linq; -using System.Security.Claims; using GraphQL.Resolvers; -using GraphQL.Server.Transports.Subscriptions.Abstractions; using GraphQL.Types; namespace GraphQL.Client.Tests.Common.Chat.Schema; @@ -60,8 +58,7 @@ public ChatSubscriptions(IChat chat) private IObservable SubscribeById(IResolveFieldContext context) { - var messageContext = (MessageHandlingContext)context.UserContext; - var user = messageContext.Get("user"); + var user = context.User; var sub = "Anonymous"; if (user != null) @@ -82,8 +79,7 @@ private Message ResolveMessage(IResolveFieldContext context) private IObservable Subscribe(IResolveFieldContext context) { - var messageContext = (MessageHandlingContext)context.UserContext; - var user = messageContext.Get("user"); + var user = context.User; var sub = "Anonymous"; if (user != null) diff --git a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj index 1b285443..d91097a6 100644 --- a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj +++ b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj @@ -7,7 +7,7 @@ - + diff --git a/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsMutation.cs b/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsMutation.cs index 161b25dd..3d57d1fe 100644 --- a/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsMutation.cs +++ b/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsMutation.cs @@ -21,12 +21,9 @@ public StarWarsMutation(StarWarsData data) { Name = "Mutation"; - Field( - "createHuman", - arguments: new QueryArguments( - new QueryArgument> { Name = "human" } - ), - resolve: context => + Field("createHuman") + .Argument>("human") + .Resolve(context => { var human = context.GetArgument("human"); return data.AddCharacter(human); diff --git a/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsQuery.cs b/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsQuery.cs index a3fb05e9..1fb231b9 100644 --- a/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsQuery.cs +++ b/tests/GraphQL.Client.Tests.Common/StarWars/StarWarsQuery.cs @@ -9,23 +9,16 @@ public StarWarsQuery(StarWarsData data) { Name = "Query"; - FieldAsync("hero", resolve: async context => await data.GetDroidByIdAsync("3")); - FieldAsync( - "human", - arguments: new QueryArguments( - new QueryArgument> { Name = "id", Description = "id of the human" } - ), - resolve: async context => await data.GetHumanByIdAsync(context.GetArgument("id")) + Field("hero").ResolveAsync(async context => await data.GetDroidByIdAsync("3")); + Field("human") + .Argument>("id", "id of the human") + .ResolveAsync(async context => await data.GetHumanByIdAsync(context.GetArgument("id")) ); Func> func = (context, id) => data.GetDroidByIdAsync(id); - FieldDelegate( - "droid", - arguments: new QueryArguments( - new QueryArgument> { Name = "id", Description = "id of the droid" } - ), - resolve: func - ); + Field("droid") + .Argument>("id", "id of the droid") + .ResolveDelegate(func); } } diff --git a/tests/GraphQL.Client.Tests.Common/StarWars/Types/CharacterInterface.cs b/tests/GraphQL.Client.Tests.Common/StarWars/Types/CharacterInterface.cs index 1e3809dc..a25daec3 100644 --- a/tests/GraphQL.Client.Tests.Common/StarWars/Types/CharacterInterface.cs +++ b/tests/GraphQL.Client.Tests.Common/StarWars/Types/CharacterInterface.cs @@ -9,11 +9,11 @@ public CharacterInterface() { Name = "Character"; - Field>("id", "The id of the character.", resolve: context => context.Source.Id); - Field("name", "The name of the character.", resolve: context => context.Source.Name); + Field>("id").Description("The id of the character.").Resolve(context => context.Source.Id); + Field("name").Description("The name of the character.").Resolve(context => context.Source.Name); Field>("friends"); Field>>("friendsConnection"); - Field>("appearsIn", "Which movie they appear in."); + Field>("appearsIn").Description("Which movie they appear in."); } } diff --git a/tests/GraphQL.Client.Tests.Common/StarWars/Types/DroidType.cs b/tests/GraphQL.Client.Tests.Common/StarWars/Types/DroidType.cs index cc9e33c3..72680c43 100644 --- a/tests/GraphQL.Client.Tests.Common/StarWars/Types/DroidType.cs +++ b/tests/GraphQL.Client.Tests.Common/StarWars/Types/DroidType.cs @@ -10,10 +10,10 @@ public DroidType(StarWarsData data) Name = "Droid"; Description = "A mechanical creature in the Star Wars universe."; - Field>("id", "The id of the droid.", resolve: context => context.Source.Id); - Field("name", "The name of the droid.", resolve: context => context.Source.Name); + Field>("id").Description("The id of the droid.").Resolve(context => context.Source.Id); + Field("name").Description("The name of the droid.").Resolve(context => context.Source.Name); - Field>("friends", resolve: context => data.GetFriends(context.Source)); + Field>("friends").Resolve(context => data.GetFriends(context.Source)); Connection() .Name("friendsConnection") @@ -21,8 +21,8 @@ public DroidType(StarWarsData data) .Bidirectional() .Resolve(context => context.GetPagedResults(data, context.Source.Friends)); - Field>("appearsIn", "Which movie they appear in."); - Field("primaryFunction", "The primary function of the droid.", resolve: context => context.Source.PrimaryFunction); + Field>("appearsIn").Description("Which movie they appear in."); + Field("primaryFunction").Description("The primary function of the droid.").Resolve(context => context.Source.PrimaryFunction); Interface(); } diff --git a/tests/GraphQL.Client.Tests.Common/StarWars/Types/HumanType.cs b/tests/GraphQL.Client.Tests.Common/StarWars/Types/HumanType.cs index 552aabdd..a95e01d0 100644 --- a/tests/GraphQL.Client.Tests.Common/StarWars/Types/HumanType.cs +++ b/tests/GraphQL.Client.Tests.Common/StarWars/Types/HumanType.cs @@ -9,10 +9,10 @@ public HumanType(StarWarsData data) { Name = "Human"; - Field>("id", "The id of the human.", resolve: context => context.Source.Id); - Field("name", "The name of the human.", resolve: context => context.Source.Name); + Field>("id").Description("The id of the human.").Resolve(context => context.Source.Id); + Field("name").Description("The name of the human.").Resolve(context => context.Source.Name); - Field>("friends", resolve: context => data.GetFriends(context.Source)); + Field>("friends").Resolve(context => data.GetFriends(context.Source)); Connection() .Name("friendsConnection") @@ -20,9 +20,9 @@ public HumanType(StarWarsData data) .Bidirectional() .Resolve(context => context.GetPagedResults(data, context.Source.Friends)); - Field>("appearsIn", "Which movie they appear in."); + Field>("appearsIn").Description("Which movie they appear in."); - Field("homePlanet", "The home planet of the human.", resolve: context => context.Source.HomePlanet); + Field("homePlanet").Description("The home planet of the human.").Resolve(context => context.Source.HomePlanet); Interface(); } diff --git a/tests/GraphQL.Integration.Tests/Properties/launchSettings.json b/tests/GraphQL.Integration.Tests/Properties/launchSettings.json index f8fea71b..3340b96e 100644 --- a/tests/GraphQL.Integration.Tests/Properties/launchSettings.json +++ b/tests/GraphQL.Integration.Tests/Properties/launchSettings.json @@ -1,12 +1,4 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:51432/", - "sslPort": 0 - } - }, "profiles": { "IIS Express": { "commandName": "IISExpress", @@ -23,5 +15,13 @@ }, "applicationUrl": "http://localhost:51433/" } + }, + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:55273/", + "sslPort": 44359 + } } } \ No newline at end of file diff --git a/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj b/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj index ac59ef1a..ba748f78 100644 --- a/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj +++ b/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj @@ -6,10 +6,9 @@ - - - - + + + diff --git a/tests/GraphQL.Server.Test/GraphQL/TestQuery.cs b/tests/GraphQL.Server.Test/GraphQL/TestQuery.cs index 31bbef6e..4e225a23 100644 --- a/tests/GraphQL.Server.Test/GraphQL/TestQuery.cs +++ b/tests/GraphQL.Server.Test/GraphQL/TestQuery.cs @@ -7,7 +7,10 @@ public class TestQuery : ObjectGraphType { public TestQuery() { - Field("repository", arguments: new QueryArguments(new QueryArgument> { Name = "owner" }, new QueryArgument> { Name = "name" }), resolve: context => + Field("repository") + .Argument>("owner") + .Argument>("name") + .Resolve(context => { var owner = context.GetArgument("owner"); var name = context.GetArgument("name"); diff --git a/tests/GraphQL.Server.Test/Startup.cs b/tests/GraphQL.Server.Test/Startup.cs index 25e1e9d5..4dd737c5 100644 --- a/tests/GraphQL.Server.Test/Startup.cs +++ b/tests/GraphQL.Server.Test/Startup.cs @@ -16,18 +16,16 @@ public void Configure(IApplicationBuilder app) app.UseHttpsRedirection(); app.UseWebSockets(); - app.UseGraphQLWebSockets(); app.UseGraphQL(); - app.UseGraphQLGraphiQL(new GraphiQLOptions { }); + app.UseGraphQLGraphiQL(); } public void ConfigureServices(IServiceCollection services) { services.AddGraphQL(builder => builder .AddSchema() - .AddApolloTracing(enableMetrics: true) - .AddErrorInfoProvider(opt => opt.ExposeExceptionStackTrace = true) - .AddWebSockets() + .UseApolloTracing(enableMetrics: true) + .AddErrorInfoProvider(opt => opt.ExposeExceptionDetails = true) ); } } diff --git a/tests/IntegrationTestServer/IntegrationTestServer.csproj b/tests/IntegrationTestServer/IntegrationTestServer.csproj index 4bda2103..4bd1a79b 100644 --- a/tests/IntegrationTestServer/IntegrationTestServer.csproj +++ b/tests/IntegrationTestServer/IntegrationTestServer.csproj @@ -11,7 +11,7 @@ - + diff --git a/tests/IntegrationTestServer/Startup.cs b/tests/IntegrationTestServer/Startup.cs index 4876fdb3..65ba8a99 100644 --- a/tests/IntegrationTestServer/Startup.cs +++ b/tests/IntegrationTestServer/Startup.cs @@ -33,20 +33,15 @@ public void ConfigureServices(IServiceCollection services) services.AddChatSchema(); services.AddStarWarsSchema(); services.AddGraphQL(builder => builder - .AddApolloTracing(enableMetrics: true) - .AddHttpMiddleware() - .AddHttpMiddleware() - .AddWebSocketsHttpMiddleware() - .AddWebSocketsHttpMiddleware() + .UseApolloTracing(enableMetrics: true) .ConfigureExecutionOptions(opt => opt.UnhandledExceptionDelegate = ctx => { var logger = ctx.Context.RequestServices.GetRequiredService>(); logger.LogError("{Error} occurred", ctx.OriginalException.Message); return System.Threading.Tasks.Task.CompletedTask; }) - .AddErrorInfoProvider(opt => opt.ExposeExceptionStackTrace = Environment.IsDevelopment()) + .AddErrorInfoProvider(opt => opt.ExposeExceptionDetails = Environment.IsDevelopment()) .AddSystemTextJson() - .AddWebSockets() .AddGraphTypes(typeof(ChatSchema).Assembly)); } @@ -63,13 +58,12 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) ConfigureGraphQLSchema(app, Common.CHAT_ENDPOINT); ConfigureGraphQLSchema(app, Common.STAR_WARS_ENDPOINT); - app.UseGraphQLGraphiQL(new GraphiQLOptions { GraphQLEndPoint = Common.STAR_WARS_ENDPOINT }); - app.UseGraphQLAltair(new AltairOptions { GraphQLEndPoint = Common.CHAT_ENDPOINT }); + app.UseGraphQLGraphiQL(options: new GraphiQLOptions { GraphQLEndPoint = Common.STAR_WARS_ENDPOINT }); + app.UseGraphQLAltair(options: new AltairOptions { GraphQLEndPoint = Common.CHAT_ENDPOINT }); } private void ConfigureGraphQLSchema(IApplicationBuilder app, string endpoint) where TSchema : Schema { - app.UseGraphQLWebSockets(endpoint); app.UseGraphQL(endpoint); } } From 444c0b9cf0b94db0ad913771e34e6437157ce9be Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Aug 2022 10:30:54 +0300 Subject: [PATCH 396/455] Bump Microsoft.NETFramework.ReferenceAssemblies from 1.0.2 to 1.0.3 (#448) Bumps [Microsoft.NETFramework.ReferenceAssemblies](https://github.com/Microsoft/dotnet) from 1.0.2 to 1.0.3. - [Release notes](https://github.com/Microsoft/dotnet/releases) - [Commits](https://github.com/Microsoft/dotnet/commits) --- updated-dependencies: - dependency-name: Microsoft.NETFramework.ReferenceAssemblies dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- src/GraphQL.Client/GraphQL.Client.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GraphQL.Client/GraphQL.Client.csproj b/src/GraphQL.Client/GraphQL.Client.csproj index 719c1a76..35dc2e16 100644 --- a/src/GraphQL.Client/GraphQL.Client.csproj +++ b/src/GraphQL.Client/GraphQL.Client.csproj @@ -14,7 +14,7 @@ - + From 99e1648bf34e62fa0459df0d652b6f1cb7980c56 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 4 Sep 2022 21:21:22 +0300 Subject: [PATCH 397/455] Bump Microsoft.NET.Test.Sdk from 17.3.0 to 17.3.1 (#457) Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.3.0 to 17.3.1. - [Release notes](https://github.com/microsoft/vstest/releases) - [Commits](https://github.com/microsoft/vstest/compare/v17.3.0...v17.3.1) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tests/tests.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tests.props b/tests/tests.props index d0969963..60859ea2 100644 --- a/tests/tests.props +++ b/tests/tests.props @@ -7,7 +7,7 @@ - + From 5cb94ca2edf6a799f1b55849dbc478ac38478dee Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Mon, 19 Sep 2022 17:16:09 +0300 Subject: [PATCH 398/455] Change media type to application/graphql-response+json (#459) --- src/GraphQL.Client/GraphQLHttpRequest.cs | 2 +- tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/GraphQL.Client/GraphQLHttpRequest.cs b/src/GraphQL.Client/GraphQLHttpRequest.cs index 2da60705..c68379c1 100644 --- a/src/GraphQL.Client/GraphQLHttpRequest.cs +++ b/src/GraphQL.Client/GraphQLHttpRequest.cs @@ -39,7 +39,7 @@ public virtual HttpRequestMessage ToHttpRequestMessage(GraphQLHttpClientOptions { Content = new StringContent(serializer.SerializeToString(this), Encoding.UTF8, options.MediaType) }; - message.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/graphql+json")); + message.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/graphql-response+json")); message.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); message.Headers.AcceptCharset.Add(new StringWithQualityHeaderValue("utf-8")); diff --git a/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs b/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs index abcdc644..bff54832 100644 --- a/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs +++ b/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs @@ -176,7 +176,7 @@ public async void PreprocessHttpRequestMessageIsCalled() var expectedHeaders = new HttpRequestMessage().Headers; expectedHeaders.UserAgent.Add(new ProductInfoHeaderValue("GraphQL.Client", typeof(GraphQLHttpClient).Assembly.GetName().Version.ToString())); - expectedHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/graphql+json")); + expectedHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/graphql-response+json")); expectedHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); expectedHeaders.AcceptCharset.Add(new StringWithQualityHeaderValue("utf-8")); var response = await StarWarsClient.SendQueryAsync(graphQLRequest, () => new { Human = new { Name = string.Empty } }); From 6970825f79b9507a422df6b87b5709cb0eed2f09 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Sep 2022 18:12:08 +0300 Subject: [PATCH 399/455] Bump GraphQL.Server.All from 7.0.0 to 7.1.1 (#461) Bumps [GraphQL.Server.All](https://github.com/graphql-dotnet/server) from 7.0.0 to 7.1.1. - [Release notes](https://github.com/graphql-dotnet/server/releases) - [Commits](https://github.com/graphql-dotnet/server/compare/7.0.0...7.1.1) --- updated-dependencies: - dependency-name: GraphQL.Server.All dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tests/IntegrationTestServer/IntegrationTestServer.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/IntegrationTestServer/IntegrationTestServer.csproj b/tests/IntegrationTestServer/IntegrationTestServer.csproj index 4bd1a79b..3098ce66 100644 --- a/tests/IntegrationTestServer/IntegrationTestServer.csproj +++ b/tests/IntegrationTestServer/IntegrationTestServer.csproj @@ -11,7 +11,7 @@ - + From b95fa5c5571cb512b0e160780277dd14bac9ea3f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Sep 2022 02:43:16 +0300 Subject: [PATCH 400/455] Bump GraphQL.NewtonsoftJson from 7.0.0 to 7.1.1 (#470) Bumps [GraphQL.NewtonsoftJson](https://github.com/graphql-dotnet/graphql-dotnet) from 7.0.0 to 7.1.1. - [Release notes](https://github.com/graphql-dotnet/graphql-dotnet/releases) - [Commits](https://github.com/graphql-dotnet/graphql-dotnet/compare/7.0.0...7.1.1) --- updated-dependencies: - dependency-name: GraphQL.NewtonsoftJson dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../GraphQL.Client.Serializer.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj b/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj index 346b7790..c7ed5465 100644 --- a/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj +++ b/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj @@ -7,7 +7,7 @@ - + From e36f1d8f1212dd31f10da5dc549f5096696796b2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Sep 2022 02:45:54 +0300 Subject: [PATCH 401/455] Bump GraphQL.SystemTextJson from 7.0.0 to 7.1.1 (#469) Bumps [GraphQL.SystemTextJson](https://github.com/graphql-dotnet/graphql-dotnet) from 7.0.0 to 7.1.1. - [Release notes](https://github.com/graphql-dotnet/graphql-dotnet/releases) - [Commits](https://github.com/graphql-dotnet/graphql-dotnet/compare/7.0.0...7.1.1) --- updated-dependencies: - dependency-name: GraphQL.SystemTextJson dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../GraphQL.Client.Serializer.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj b/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj index c7ed5465..a3ddb485 100644 --- a/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj +++ b/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj @@ -8,7 +8,7 @@ - + From ff71edae518fb4715ae4ebdd5fac4e101e1cec43 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Sep 2022 02:47:38 +0300 Subject: [PATCH 402/455] Bump GraphQL.Server.Ui.GraphiQL from 7.0.0 to 7.1.1 (#472) Bumps [GraphQL.Server.Ui.GraphiQL](https://github.com/graphql-dotnet/server) from 7.0.0 to 7.1.1. - [Release notes](https://github.com/graphql-dotnet/server/releases) - [Commits](https://github.com/graphql-dotnet/server/compare/7.0.0...7.1.1) --- updated-dependencies: - dependency-name: GraphQL.Server.Ui.GraphiQL dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj b/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj index ba748f78..a14933c0 100644 --- a/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj +++ b/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj @@ -8,7 +8,7 @@ - + From 8046a9e2cbc972350ddb2c375ab69dc024c591e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Sep 2022 02:47:59 +0300 Subject: [PATCH 403/455] Bump Microsoft.AspNetCore.Mvc.Testing from 6.0.8 to 6.0.9 (#473) Bumps [Microsoft.AspNetCore.Mvc.Testing](https://github.com/dotnet/aspnetcore) from 6.0.8 to 6.0.9. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md) - [Commits](https://github.com/dotnet/aspnetcore/compare/v6.0.8...v6.0.9) --- updated-dependencies: - dependency-name: Microsoft.AspNetCore.Mvc.Testing dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj b/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj index 46a82161..37e62240 100644 --- a/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj +++ b/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj @@ -7,7 +7,7 @@ - + From 9693afde8e965fe9ff872f6fef0b8a2a57e8fc38 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Sep 2022 02:48:17 +0300 Subject: [PATCH 404/455] Bump GraphQL from 7.0.0 to 7.1.1 (#466) Bumps [GraphQL](https://github.com/graphql-dotnet/graphql-dotnet) from 7.0.0 to 7.1.1. - [Release notes](https://github.com/graphql-dotnet/graphql-dotnet/releases) - [Commits](https://github.com/graphql-dotnet/graphql-dotnet/compare/7.0.0...7.1.1) --- updated-dependencies: - dependency-name: GraphQL dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../GraphQL.Client.Tests.Common.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj index d91097a6..de4ef937 100644 --- a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj +++ b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj @@ -7,7 +7,7 @@ - + From 8b970436a05c7670e5bb887128f19c7adb610fad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Sep 2022 02:55:46 +0300 Subject: [PATCH 405/455] Bump GraphQL.Server.Transports.AspNetCore from 7.0.0 to 7.1.1 (#467) * Bump GraphQL.Server.Transports.AspNetCore from 7.0.0 to 7.1.1 Bumps [GraphQL.Server.Transports.AspNetCore](https://github.com/graphql-dotnet/server) from 7.0.0 to 7.1.1. - [Release notes](https://github.com/graphql-dotnet/server/releases) - [Commits](https://github.com/graphql-dotnet/server/compare/7.0.0...7.1.1) --- updated-dependencies: - dependency-name: GraphQL.Server.Transports.AspNetCore dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Update tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ivan Maximov --- tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj b/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj index a14933c0..20b2fdb0 100644 --- a/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj +++ b/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj @@ -6,8 +6,8 @@ - - + + From 7a2a621781bf46c3dacd471e801f4915481325a1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Oct 2022 22:07:32 +0300 Subject: [PATCH 406/455] Bump actions/setup-dotnet from 2 to 3 (#476) Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 2 to 3. - [Release notes](https://github.com/actions/setup-dotnet/releases) - [Commits](https://github.com/actions/setup-dotnet/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/setup-dotnet dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/branches.yml | 2 +- .github/workflows/master.yml | 2 +- .github/workflows/publish.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/branches.yml b/.github/workflows/branches.yml index 3807f0a0..c4e92d0d 100644 --- a/.github/workflows/branches.yml +++ b/.github/workflows/branches.yml @@ -22,7 +22,7 @@ jobs: with: fetch-depth: 0 - name: Setup .NET Core SDK - uses: actions/setup-dotnet@v2 + uses: actions/setup-dotnet@v3 with: dotnet-version: | 6.0.x diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 31cc1f10..db8264ec 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -21,7 +21,7 @@ jobs: with: fetch-depth: 0 - name: Setup .NET Core SDK - uses: actions/setup-dotnet@v2 + uses: actions/setup-dotnet@v3 with: dotnet-version: "6.0.x" source-url: https://nuget.pkg.github.com/graphql-dotnet/index.json diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ddff922e..98a85b0c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -25,7 +25,7 @@ jobs: echo github.ref: ${{ github.ref }} exit 1 - name: Setup .NET Core SDK - uses: actions/setup-dotnet@v2 + uses: actions/setup-dotnet@v3 with: dotnet-version: "6.0.x" source-url: https://api.nuget.org/v3/index.json From 126f120712d66522c6941e5c41e809a07d8ed814 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Oct 2022 22:10:30 +0300 Subject: [PATCH 407/455] Bump Microsoft.NET.Test.Sdk from 17.3.1 to 17.3.2 (#474) Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.3.1 to 17.3.2. - [Release notes](https://github.com/microsoft/vstest/releases) - [Commits](https://github.com/microsoft/vstest/compare/v17.3.1...v17.3.2) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tests/tests.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tests.props b/tests/tests.props index 60859ea2..1e07f81c 100644 --- a/tests/tests.props +++ b/tests/tests.props @@ -7,7 +7,7 @@ - + From 6cb1408346ce659c64612aa8fd567bccaa17e904 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Oct 2022 21:53:29 +0300 Subject: [PATCH 408/455] Bump coverlet.collector from 3.1.2 to 3.2.0 (#482) Bumps [coverlet.collector](https://github.com/coverlet-coverage/coverlet) from 3.1.2 to 3.2.0. - [Release notes](https://github.com/coverlet-coverage/coverlet/releases) - [Commits](https://github.com/coverlet-coverage/coverlet/commits/v3.2.0) --- updated-dependencies: - dependency-name: coverlet.collector dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tests/tests.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tests.props b/tests/tests.props index 1e07f81c..26e1310b 100644 --- a/tests/tests.props +++ b/tests/tests.props @@ -10,7 +10,7 @@ - + all runtime;build;native;contentfiles;analyzers;buildtransitive From a1d4e0bcc93001d5966922663a7e60777af27678 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Nov 2022 10:55:49 +0300 Subject: [PATCH 409/455] Bump Microsoft.Extensions.DependencyInjection from 6.0.0 to 6.0.1 (#478) Bumps [Microsoft.Extensions.DependencyInjection](https://github.com/dotnet/runtime) from 6.0.0 to 6.0.1. - [Release notes](https://github.com/dotnet/runtime/releases) - [Commits](https://github.com/dotnet/runtime/compare/v6.0.0...v6.0.1) --- updated-dependencies: - dependency-name: Microsoft.Extensions.DependencyInjection dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../GraphQL.Client.Tests.Common.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj index de4ef937..c8224fbf 100644 --- a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj +++ b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj @@ -8,7 +8,7 @@ - + From a611eb590ca06cd4f0950793d6fd2349e4b80181 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Nov 2022 10:56:08 +0300 Subject: [PATCH 410/455] Bump Microsoft.AspNetCore.Mvc.Testing from 6.0.9 to 6.0.10 (#479) Bumps [Microsoft.AspNetCore.Mvc.Testing](https://github.com/dotnet/aspnetcore) from 6.0.9 to 6.0.10. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md) - [Commits](https://github.com/dotnet/aspnetcore/compare/v6.0.9...v6.0.10) --- updated-dependencies: - dependency-name: Microsoft.AspNetCore.Mvc.Testing dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj b/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj index 37e62240..cb2e12d0 100644 --- a/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj +++ b/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj @@ -7,7 +7,7 @@ - + From 1bbbb18bf657e4ae80a9af7a402f0b6dc5c287e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Nov 2022 03:20:08 +0300 Subject: [PATCH 411/455] Bump Microsoft.NET.Test.Sdk from 17.3.2 to 17.4.0 (#488) Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.3.2 to 17.4.0. - [Release notes](https://github.com/microsoft/vstest/releases) - [Commits](https://github.com/microsoft/vstest/compare/v17.3.2...v17.4.0) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tests/tests.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tests.props b/tests/tests.props index 26e1310b..6d3c5916 100644 --- a/tests/tests.props +++ b/tests/tests.props @@ -7,7 +7,7 @@ - + From accbc050171e2b7f87b737ab7c81ecfddb38ef65 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Nov 2022 18:51:26 +0300 Subject: [PATCH 412/455] Bump Newtonsoft.Json from 13.0.1 to 13.0.2 (#490) Bumps [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json) from 13.0.1 to 13.0.2. - [Release notes](https://github.com/JamesNK/Newtonsoft.Json/releases) - [Commits](https://github.com/JamesNK/Newtonsoft.Json/compare/13.0.1...13.0.2) --- updated-dependencies: - dependency-name: Newtonsoft.Json dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../GraphQL.Client.Serializer.Newtonsoft.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GraphQL.Client.Serializer.Newtonsoft/GraphQL.Client.Serializer.Newtonsoft.csproj b/src/GraphQL.Client.Serializer.Newtonsoft/GraphQL.Client.Serializer.Newtonsoft.csproj index 65aef0d0..a3c9700a 100644 --- a/src/GraphQL.Client.Serializer.Newtonsoft/GraphQL.Client.Serializer.Newtonsoft.csproj +++ b/src/GraphQL.Client.Serializer.Newtonsoft/GraphQL.Client.Serializer.Newtonsoft.csproj @@ -6,7 +6,7 @@ - + From 3598bf1e4032ea06cf4fe5d42348d13db88765b8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Dec 2022 20:16:07 +0300 Subject: [PATCH 413/455] Bump GraphQL from 7.1.1 to 7.2.0 (#495) Bumps [GraphQL](https://github.com/graphql-dotnet/graphql-dotnet) from 7.1.1 to 7.2.0. - [Release notes](https://github.com/graphql-dotnet/graphql-dotnet/releases) - [Commits](https://github.com/graphql-dotnet/graphql-dotnet/compare/7.1.1...7.2.0) --- updated-dependencies: - dependency-name: GraphQL dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../GraphQL.Client.Tests.Common.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj index c8224fbf..7f1ca7b5 100644 --- a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj +++ b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj @@ -7,7 +7,7 @@ - + From 0fa1326806ea930cfa5a5c3c80d87a08c776fa9f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Dec 2022 01:17:23 +0300 Subject: [PATCH 414/455] Bump GraphQL from 7.2.0 to 7.2.1 (#498) Bumps [GraphQL](https://github.com/graphql-dotnet/graphql-dotnet) from 7.2.0 to 7.2.1. - [Release notes](https://github.com/graphql-dotnet/graphql-dotnet/releases) - [Commits](https://github.com/graphql-dotnet/graphql-dotnet/compare/7.2.0...7.2.1) --- updated-dependencies: - dependency-name: GraphQL dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../GraphQL.Client.Tests.Common.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj index 7f1ca7b5..c907109f 100644 --- a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj +++ b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj @@ -7,7 +7,7 @@ - + From 8dc771b1abe9c3c6fa7bfd88255bb3342d735e42 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Dec 2022 01:46:36 +0300 Subject: [PATCH 415/455] Bump Microsoft.Extensions.DependencyInjection from 6.0.1 to 7.0.0 (#489) Bumps [Microsoft.Extensions.DependencyInjection](https://github.com/dotnet/runtime) from 6.0.1 to 7.0.0. - [Release notes](https://github.com/dotnet/runtime/releases) - [Commits](https://github.com/dotnet/runtime/commits) --- updated-dependencies: - dependency-name: Microsoft.Extensions.DependencyInjection dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../GraphQL.Client.Tests.Common.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj index c907109f..d2ec70dc 100644 --- a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj +++ b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj @@ -8,7 +8,7 @@ - + From c1610d9d34550b447fe11b2d9dca806ba86d9e13 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Jan 2023 02:21:21 +0300 Subject: [PATCH 416/455] Bump GraphQL from 7.2.1 to 7.2.2 (#510) Bumps [GraphQL](https://github.com/graphql-dotnet/graphql-dotnet) from 7.2.1 to 7.2.2. - [Release notes](https://github.com/graphql-dotnet/graphql-dotnet/releases) - [Commits](https://github.com/graphql-dotnet/graphql-dotnet/compare/7.2.1...7.2.2) --- updated-dependencies: - dependency-name: GraphQL dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../GraphQL.Client.Tests.Common.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj index d2ec70dc..0115cf36 100644 --- a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj +++ b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj @@ -7,7 +7,7 @@ - + From 659b1b65b992ca8434ad0e341576bf66379d76db Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Jan 2023 02:25:49 +0300 Subject: [PATCH 417/455] Bump GraphQL.SystemTextJson from 7.1.1 to 7.2.2 (#511) Bumps [GraphQL.SystemTextJson](https://github.com/graphql-dotnet/graphql-dotnet) from 7.1.1 to 7.2.2. - [Release notes](https://github.com/graphql-dotnet/graphql-dotnet/releases) - [Commits](https://github.com/graphql-dotnet/graphql-dotnet/compare/7.1.1...7.2.2) --- updated-dependencies: - dependency-name: GraphQL.SystemTextJson dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../GraphQL.Client.Serializer.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj b/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj index a3ddb485..b81837c9 100644 --- a/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj +++ b/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj @@ -8,7 +8,7 @@ - + From 1f96b7e6bcee9fdfb73b7e59e4f427e6ad986eeb Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Sun, 2 Apr 2023 10:16:53 +0300 Subject: [PATCH 418/455] .NET 7 (#528) --- .github/workflows/branches.yml | 6 +++--- .github/workflows/master.yml | 4 ++-- .github/workflows/publish.yml | 4 ++-- .../GraphQL.Client.Example/GraphQL.Client.Example.csproj | 2 +- .../GraphQL.Client.Serializer.Tests.csproj | 2 +- .../GraphQL.Integration.Tests.csproj | 2 +- .../GraphQL.Primitives.Tests.csproj | 2 +- tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj | 2 +- tests/IntegrationTestServer/IntegrationTestServer.csproj | 2 +- 9 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/branches.yml b/.github/workflows/branches.yml index c4e92d0d..494f1c75 100644 --- a/.github/workflows/branches.yml +++ b/.github/workflows/branches.yml @@ -21,11 +21,11 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 0 - - name: Setup .NET Core SDK + - name: Setup .NET SDK uses: actions/setup-dotnet@v3 with: - dotnet-version: | - 6.0.x + dotnet-version: | + 7.0.x - name: Restore dotnet tools run: dotnet tool restore - name: Fetch complete repository including tags diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index db8264ec..3fc48c41 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -20,10 +20,10 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 0 - - name: Setup .NET Core SDK + - name: Setup .NET SDK uses: actions/setup-dotnet@v3 with: - dotnet-version: "6.0.x" + dotnet-version: "7.0.x" source-url: https://nuget.pkg.github.com/graphql-dotnet/index.json env: NUGET_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 98a85b0c..a7ebfb2e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -24,10 +24,10 @@ jobs: echo Error! github.ref does not start with 'refs/tags' echo github.ref: ${{ github.ref }} exit 1 - - name: Setup .NET Core SDK + - name: Setup .NET SDK uses: actions/setup-dotnet@v3 with: - dotnet-version: "6.0.x" + dotnet-version: "7.0.x" source-url: https://api.nuget.org/v3/index.json env: NUGET_AUTH_TOKEN: ${{secrets.NUGET_API_KEY}} diff --git a/examples/GraphQL.Client.Example/GraphQL.Client.Example.csproj b/examples/GraphQL.Client.Example/GraphQL.Client.Example.csproj index 046e466d..28c2c457 100644 --- a/examples/GraphQL.Client.Example/GraphQL.Client.Example.csproj +++ b/examples/GraphQL.Client.Example/GraphQL.Client.Example.csproj @@ -2,7 +2,7 @@ Exe - net6 + net7 false diff --git a/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj b/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj index b81837c9..05d14939 100644 --- a/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj +++ b/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj @@ -3,7 +3,7 @@ - net6 + net7 diff --git a/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj b/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj index cb2e12d0..8dcc9e27 100644 --- a/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj +++ b/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj @@ -3,7 +3,7 @@ - net6 + net7 diff --git a/tests/GraphQL.Primitives.Tests/GraphQL.Primitives.Tests.csproj b/tests/GraphQL.Primitives.Tests/GraphQL.Primitives.Tests.csproj index ad5efd71..235c025a 100644 --- a/tests/GraphQL.Primitives.Tests/GraphQL.Primitives.Tests.csproj +++ b/tests/GraphQL.Primitives.Tests/GraphQL.Primitives.Tests.csproj @@ -3,7 +3,7 @@ - net6 + net7 diff --git a/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj b/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj index 20b2fdb0..1d1d8ed3 100644 --- a/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj +++ b/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj @@ -1,7 +1,7 @@ - net6 + net7 false diff --git a/tests/IntegrationTestServer/IntegrationTestServer.csproj b/tests/IntegrationTestServer/IntegrationTestServer.csproj index 3098ce66..ac852617 100644 --- a/tests/IntegrationTestServer/IntegrationTestServer.csproj +++ b/tests/IntegrationTestServer/IntegrationTestServer.csproj @@ -1,7 +1,7 @@ - net6 + net7 IntegrationTestServer.Program false From 3967053fb13a51686d99dfe011fcd757084c0ed1 Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Sun, 2 Apr 2023 10:25:42 +0300 Subject: [PATCH 419/455] Update packages for tests (#529) --- Directory.Build.targets | 6 +++--- .../GraphQL.Client.Serializer.Tests.csproj | 4 ++-- .../GraphQL.Client.Tests.Common.csproj | 2 +- .../GraphQL.Integration.Tests.csproj | 2 +- tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj | 6 +++--- tests/IntegrationTestServer/IntegrationTestServer.csproj | 2 +- tests/tests.props | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Directory.Build.targets b/Directory.Build.targets index af739e1d..a75e6500 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -30,9 +30,9 @@ - - - + + + all diff --git a/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj b/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj index 05d14939..baaf2c01 100644 --- a/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj +++ b/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj @@ -7,8 +7,8 @@ - - + + diff --git a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj index 0115cf36..f8069a7b 100644 --- a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj +++ b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj @@ -7,7 +7,7 @@ - + diff --git a/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj b/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj index 8dcc9e27..2ede20bc 100644 --- a/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj +++ b/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj @@ -7,7 +7,7 @@ - + diff --git a/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj b/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj index 1d1d8ed3..901724ee 100644 --- a/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj +++ b/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj @@ -6,9 +6,9 @@ - - - + + + diff --git a/tests/IntegrationTestServer/IntegrationTestServer.csproj b/tests/IntegrationTestServer/IntegrationTestServer.csproj index ac852617..d26b2adb 100644 --- a/tests/IntegrationTestServer/IntegrationTestServer.csproj +++ b/tests/IntegrationTestServer/IntegrationTestServer.csproj @@ -11,7 +11,7 @@ - + diff --git a/tests/tests.props b/tests/tests.props index 6d3c5916..87ec1373 100644 --- a/tests/tests.props +++ b/tests/tests.props @@ -7,7 +7,7 @@ - + From 4e0c885cf4db07d9bfd1f9c186f119d100a54668 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Apr 2023 21:36:43 +0300 Subject: [PATCH 420/455] Bump GraphQL from 7.3.0 to 7.3.1 (#535) Bumps [GraphQL](https://github.com/graphql-dotnet/graphql-dotnet) from 7.3.0 to 7.3.1. - [Release notes](https://github.com/graphql-dotnet/graphql-dotnet/releases) - [Commits](https://github.com/graphql-dotnet/graphql-dotnet/compare/7.3.0...7.3.1) --- updated-dependencies: - dependency-name: GraphQL dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../GraphQL.Client.Tests.Common.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj index f8069a7b..9bfadf6b 100644 --- a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj +++ b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj @@ -7,7 +7,7 @@ - + From 3f79cd30a8bcecb208755aa7ccc20fb445b4eb2e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Apr 2023 21:37:26 +0300 Subject: [PATCH 421/455] Bump GraphQL.NewtonsoftJson from 7.3.0 to 7.3.1 (#534) Bumps [GraphQL.NewtonsoftJson](https://github.com/graphql-dotnet/graphql-dotnet) from 7.3.0 to 7.3.1. - [Release notes](https://github.com/graphql-dotnet/graphql-dotnet/releases) - [Commits](https://github.com/graphql-dotnet/graphql-dotnet/compare/7.3.0...7.3.1) --- updated-dependencies: - dependency-name: GraphQL.NewtonsoftJson dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../GraphQL.Client.Serializer.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj b/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj index baaf2c01..667638d3 100644 --- a/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj +++ b/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj @@ -7,7 +7,7 @@ - + From ebc4b8205742d6c71cf92d1eae81d9aa48a83a62 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Apr 2023 21:39:49 +0300 Subject: [PATCH 422/455] Bump GraphQL.SystemTextJson from 7.3.0 to 7.3.1 (#531) Bumps [GraphQL.SystemTextJson](https://github.com/graphql-dotnet/graphql-dotnet) from 7.3.0 to 7.3.1. - [Release notes](https://github.com/graphql-dotnet/graphql-dotnet/releases) - [Commits](https://github.com/graphql-dotnet/graphql-dotnet/compare/7.3.0...7.3.1) --- updated-dependencies: - dependency-name: GraphQL.SystemTextJson dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../GraphQL.Client.Serializer.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj b/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj index 667638d3..fac5bf23 100644 --- a/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj +++ b/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj @@ -8,7 +8,7 @@ - + From 06bf968ae7956fd4d58d79d3bb7abff1a4dd51e9 Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Thu, 6 Apr 2023 18:13:18 +0300 Subject: [PATCH 423/455] Format repo with editorconfig (#530) --- .editorconfig | 49 +++++++--- .../PersonAndFilmsResponse.cs | 2 - examples/GraphQL.Client.Example/Program.cs | 3 - .../Utilities/StringExtensions.cs | 2 +- .../Utilities/StringUtils.cs | 2 +- .../NewtonsoftJsonSerializer.cs | 4 +- .../ConstantCaseJsonNamingPolicy.cs | 2 +- .../ErrorPathConverter.cs | 8 +- .../MapConverter.cs | 6 +- .../SystemTextJsonSerializer.cs | 2 +- src/GraphQL.Client/GraphQLHttpClient.cs | 28 +++--- src/GraphQL.Client/GraphQLHttpRequest.cs | 2 +- src/GraphQL.Client/GraphQLHttpResponse.cs | 2 +- src/GraphQL.Client/UriExtensions.cs | 4 +- .../Websocket/GraphQLHttpWebSocket.cs | 91 ++++++++++--------- .../GraphQLWebsocketConnectionException.cs | 2 +- src/GraphQL.Primitives/GraphQLRequest.cs | 2 +- .../BaseSerializeNoCamelCaseTest.cs | 1 - .../BaseSerializerTest.cs | 5 - .../ConsistencyTests.cs | 7 +- .../SystemTextJsonSerializerTests.cs | 2 +- .../TestData/DeserializeResponseTestData.cs | 3 +- .../TestData/SerializeToBytesTestData.cs | 1 - .../TestData/SerializeToStringTestData.cs | 3 - .../Helpers/CallbackMonitor.cs | 2 +- .../Helpers/ConcurrentTaskWrapper.cs | 12 +-- .../UriExtensionTests.cs | 2 +- .../WebsocketTests/Base.cs | 26 +++--- .../JsonSerializationTests.cs | 1 - tests/GraphQL.Server.Test/Startup.cs | 2 - tests/IntegrationTestServer/Startup.cs | 16 +--- 31 files changed, 144 insertions(+), 150 deletions(-) diff --git a/.editorconfig b/.editorconfig index b3d87c9d..c05bd66d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,16 +4,16 @@ # https://docs.microsoft.com/en-us/visualstudio/ide/create-portable-custom-editor-options # .NET coding convention settings for EditorConfig -# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference?view=vs-2019 +# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference # Language conventions -# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019 +# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions # Formatting conventions -# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-formatting-conventions?view=vs-2019 +# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-formatting-conventions # .NET naming conventions for EditorConfig -# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-naming-conventions?view=vs-2019 +# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-naming-conventions # Top-most EditorConfig file root = true @@ -26,6 +26,9 @@ indent_style = space indent_size = 2 trim_trailing_whitespace = true +[*.json] +insert_final_newline = false + [*.cs] indent_size = 4 @@ -91,8 +94,8 @@ csharp_style_var_elsewhere = true:suggestion # C# code style settings - Expression-bodied members # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#expression-bodied-members -csharp_style_expression_bodied_methods = when_on_single_line:warning -csharp_style_expression_bodied_constructors = false:suggestion +csharp_style_expression_bodied_methods = when_on_single_line:suggestion +csharp_style_expression_bodied_constructors = false:warning csharp_style_expression_bodied_operators = when_on_single_line:warning csharp_style_expression_bodied_properties = when_on_single_line:warning csharp_style_expression_bodied_indexers = when_on_single_line:warning @@ -120,7 +123,7 @@ csharp_style_conditional_delegate_call = true:warning # C# code style settings - Code block preferences # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#code-block-preferences -csharp_prefer_braces = false:suggestion +csharp_prefer_braces = when_multiline:suggestion # C# code style - Unused value preferences # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#unused-value-preferences @@ -129,8 +132,8 @@ csharp_style_unused_value_assignment_preference = discard_variable:suggestion # C# code style - Index and range preferences # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#index-and-range-preferences -csharp_style_prefer_index_operator = true:warning -csharp_style_prefer_range_operator = true:warning +csharp_style_prefer_index_operator = true:suggestion +csharp_style_prefer_range_operator = true:suggestion # C# code style - Miscellaneous preferences # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions?view=vs-2019#miscellaneous-preferences @@ -138,8 +141,8 @@ csharp_style_deconstructed_variable_declaration = true:suggestion csharp_style_pattern_local_over_anonymous_function = true:suggestion csharp_using_directive_placement = outside_namespace:warning csharp_prefer_static_local_function = true:suggestion -csharp_prefer_simple_using_statement = false:suggestion -csharp_style_prefer_switch_expression = true:suggestion +csharp_prefer_simple_using_statement = true:warning +csharp_style_prefer_switch_expression = true:warning # .NET formatting settings - Organize using directives # https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-formatting-conventions?view=vs-2019#organize-using-directives @@ -260,3 +263,27 @@ dotnet_naming_rule.async_methods_end_in_async.severity = warning # ReSharper: Configure await configure_await_analysis_mode = library + +# Remove unnecessary import https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0005 +dotnet_diagnostic.IDE0005.severity = error + +# Enforce formatting https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#rule-id-ide0055-fix-formatting +dotnet_diagnostic.IDE0055.severity = error + +# https://github.com/JosefPihrt/Roslynator/blob/master/docs/analyzers/RCS0060.md +dotnet_diagnostic.RCS0060.severity = warning +roslynator_blank_line_after_file_scoped_namespace_declaration = true + +# https://github.com/JosefPihrt/Roslynator/blob/main/docs/analyzers/RCS1080.md +dotnet_diagnostic.RCS1080.severity = warning + +# ConfigureAwait https://github.com/JosefPihrt/Roslynator/blob/master/docs/analyzers/RCS1090.md +dotnet_diagnostic.RCS1090.severity = warning +roslynator_configure_await = true + +# https://github.com/JosefPihrt/Roslynator/blob/master/docs/analyzers/RCS1102.md +# TODO: NullabilityInfo issue in Patching.cs in internal class SR +dotnet_diagnostic.RCS1102.severity = suggestion + +# https://github.com/JosefPihrt/Roslynator/blob/master/docs/analyzers/RCS1194.md +dotnet_diagnostic.RCS1194.severity = suggestion diff --git a/examples/GraphQL.Client.Example/PersonAndFilmsResponse.cs b/examples/GraphQL.Client.Example/PersonAndFilmsResponse.cs index 7dbd22b5..3a66edc7 100644 --- a/examples/GraphQL.Client.Example/PersonAndFilmsResponse.cs +++ b/examples/GraphQL.Client.Example/PersonAndFilmsResponse.cs @@ -1,5 +1,3 @@ -using System.Collections.Generic; - namespace GraphQL.Client.Example; public class PersonAndFilmsResponse diff --git a/examples/GraphQL.Client.Example/Program.cs b/examples/GraphQL.Client.Example/Program.cs index b1f4d2ac..83f8da0a 100644 --- a/examples/GraphQL.Client.Example/Program.cs +++ b/examples/GraphQL.Client.Example/Program.cs @@ -1,7 +1,4 @@ -using System; -using System.Linq; using System.Text.Json; -using System.Threading.Tasks; using GraphQL.Client.Http; using GraphQL.Client.Serializer.Newtonsoft; diff --git a/src/GraphQL.Client.Abstractions/Utilities/StringExtensions.cs b/src/GraphQL.Client.Abstractions/Utilities/StringExtensions.cs index f751adb8..cc99a85e 100644 --- a/src/GraphQL.Client.Abstractions/Utilities/StringExtensions.cs +++ b/src/GraphQL.Client.Abstractions/Utilities/StringExtensions.cs @@ -1,4 +1,4 @@ -namespace GraphQL.Client.Abstractions.Utilities; +namespace GraphQL.Client.Abstractions.Utilities; /// /// Copied from https://github.com/jquense/StringUtils diff --git a/src/GraphQL.Client.Abstractions/Utilities/StringUtils.cs b/src/GraphQL.Client.Abstractions/Utilities/StringUtils.cs index 27fbf992..87047f79 100644 --- a/src/GraphQL.Client.Abstractions/Utilities/StringUtils.cs +++ b/src/GraphQL.Client.Abstractions/Utilities/StringUtils.cs @@ -1,4 +1,4 @@ -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; namespace GraphQL.Client.Abstractions.Utilities; diff --git a/src/GraphQL.Client.Serializer.Newtonsoft/NewtonsoftJsonSerializer.cs b/src/GraphQL.Client.Serializer.Newtonsoft/NewtonsoftJsonSerializer.cs index 63cc6de3..0cfd14b1 100644 --- a/src/GraphQL.Client.Serializer.Newtonsoft/NewtonsoftJsonSerializer.cs +++ b/src/GraphQL.Client.Serializer.Newtonsoft/NewtonsoftJsonSerializer.cs @@ -8,7 +8,7 @@ namespace GraphQL.Client.Serializer.Newtonsoft; public class NewtonsoftJsonSerializer : IGraphQLWebsocketJsonSerializer { - public static JsonSerializerSettings DefaultJsonSerializerSettings => new JsonSerializerSettings + public static JsonSerializerSettings DefaultJsonSerializerSettings => new() { ContractResolver = new CamelCasePropertyNamesContractResolver { IgnoreIsSpecifiedMembers = true }, MissingMemberHandling = MissingMemberHandling.Ignore, @@ -34,7 +34,7 @@ public NewtonsoftJsonSerializer(JsonSerializerSettings jsonSerializerSettings) public byte[] SerializeToBytes(GraphQLWebSocketRequest request) { - var json = JsonConvert.SerializeObject(request, JsonSerializerSettings); + string json = JsonConvert.SerializeObject(request, JsonSerializerSettings); return Encoding.UTF8.GetBytes(json); } diff --git a/src/GraphQL.Client.Serializer.SystemTextJson/ConstantCaseJsonNamingPolicy.cs b/src/GraphQL.Client.Serializer.SystemTextJson/ConstantCaseJsonNamingPolicy.cs index 680503a6..acc7f9b1 100644 --- a/src/GraphQL.Client.Serializer.SystemTextJson/ConstantCaseJsonNamingPolicy.cs +++ b/src/GraphQL.Client.Serializer.SystemTextJson/ConstantCaseJsonNamingPolicy.cs @@ -3,7 +3,7 @@ namespace GraphQL.Client.Serializer.SystemTextJson; -public class ConstantCaseJsonNamingPolicy: JsonNamingPolicy +public class ConstantCaseJsonNamingPolicy : JsonNamingPolicy { public override string ConvertName(string name) => name.ToConstantCase(); } diff --git a/src/GraphQL.Client.Serializer.SystemTextJson/ErrorPathConverter.cs b/src/GraphQL.Client.Serializer.SystemTextJson/ErrorPathConverter.cs index e325dccb..b50a8591 100644 --- a/src/GraphQL.Client.Serializer.SystemTextJson/ErrorPathConverter.cs +++ b/src/GraphQL.Client.Serializer.SystemTextJson/ErrorPathConverter.cs @@ -7,13 +7,13 @@ public class ErrorPathConverter : JsonConverter { public override ErrorPath Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => - new ErrorPath(ReadArray(ref reader)); + new(ReadArray(ref reader)); public override void Write(Utf8JsonWriter writer, ErrorPath value, JsonSerializerOptions options) => throw new NotImplementedException( "This converter currently is only intended to be used to read a JSON object into a strongly-typed representation."); - - private IEnumerable ReadArray(ref Utf8JsonReader reader) + + private static IEnumerable ReadArray(ref Utf8JsonReader reader) { if (reader.TokenType != JsonTokenType.StartArray) { @@ -33,7 +33,7 @@ public override void Write(Utf8JsonWriter writer, ErrorPath value, JsonSerialize return array; } - private object? ReadValue(ref Utf8JsonReader reader) + private static object? ReadValue(ref Utf8JsonReader reader) => reader.TokenType switch { JsonTokenType.None => null, diff --git a/src/GraphQL.Client.Serializer.SystemTextJson/MapConverter.cs b/src/GraphQL.Client.Serializer.SystemTextJson/MapConverter.cs index 66d4aead..78eef361 100644 --- a/src/GraphQL.Client.Serializer.SystemTextJson/MapConverter.cs +++ b/src/GraphQL.Client.Serializer.SystemTextJson/MapConverter.cs @@ -22,7 +22,7 @@ private static TDictionary ReadDictionary(ref Utf8JsonReader reader { if (reader.TokenType != JsonTokenType.StartObject) throw new JsonException(); - + while (reader.Read()) { if (reader.TokenType == JsonTokenType.EndObject) @@ -60,7 +60,7 @@ private static List ReadArray(ref Utf8JsonReader reader) return result; } - + private static object? ReadValue(ref Utf8JsonReader reader) => reader.TokenType switch { @@ -74,6 +74,4 @@ private static List ReadArray(ref Utf8JsonReader reader) JsonTokenType.None => null, _ => throw new InvalidOperationException($"Unexpected value kind: {reader.TokenType}") }; - - } diff --git a/src/GraphQL.Client.Serializer.SystemTextJson/SystemTextJsonSerializer.cs b/src/GraphQL.Client.Serializer.SystemTextJson/SystemTextJsonSerializer.cs index 4fa66c64..232fa421 100644 --- a/src/GraphQL.Client.Serializer.SystemTextJson/SystemTextJsonSerializer.cs +++ b/src/GraphQL.Client.Serializer.SystemTextJson/SystemTextJsonSerializer.cs @@ -10,7 +10,7 @@ public class SystemTextJsonSerializer : IGraphQLWebsocketJsonSerializer public static JsonSerializerOptions DefaultJsonSerializerOptions => new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - Converters = { new JsonStringEnumConverter(new ConstantCaseJsonNamingPolicy(), false)} + Converters = { new JsonStringEnumConverter(new ConstantCaseJsonNamingPolicy(), false) } }.SetupImmutableConverter(); public JsonSerializerOptions Options { get; } diff --git a/src/GraphQL.Client/GraphQLHttpClient.cs b/src/GraphQL.Client/GraphQLHttpClient.cs index eda4b6f9..e42cd678 100644 --- a/src/GraphQL.Client/GraphQLHttpClient.cs +++ b/src/GraphQL.Client/GraphQLHttpClient.cs @@ -77,12 +77,9 @@ public GraphQLHttpClient(GraphQLHttpClientOptions options, IGraphQLWebsocketJson /// public async Task> SendQueryAsync(GraphQLRequest request, CancellationToken cancellationToken = default) { - if (Options.UseWebSocketForQueriesAndMutations || - !(Options.WebSocketEndPoint is null) && Options.EndPoint is null || - Options.EndPoint.HasWebSocketScheme()) - return await GraphQlHttpWebSocket.SendRequest(request, cancellationToken).ConfigureAwait(false); - - return await SendHttpRequestAsync(request, cancellationToken).ConfigureAwait(false); + return Options.UseWebSocketForQueriesAndMutations || Options.WebSocketEndPoint is not null && Options.EndPoint is null || Options.EndPoint.HasWebSocketScheme() + ? await GraphQlHttpWebSocket.SendRequest(request, cancellationToken).ConfigureAwait(false) + : await SendHttpRequestAsync(request, cancellationToken).ConfigureAwait(false); } /// @@ -132,22 +129,23 @@ private async Task> SendHttpRequestAsync response, HttpResponseHeaders resp public static class GraphQLResponseExtensions { - public static GraphQLHttpResponse ToGraphQLHttpResponse(this GraphQLResponse response, HttpResponseHeaders responseHeaders, HttpStatusCode statusCode) => new GraphQLHttpResponse(response, responseHeaders, statusCode); + public static GraphQLHttpResponse ToGraphQLHttpResponse(this GraphQLResponse response, HttpResponseHeaders responseHeaders, HttpStatusCode statusCode) => new(response, responseHeaders, statusCode); /// /// Casts to . Throws if the cast fails. diff --git a/src/GraphQL.Client/UriExtensions.cs b/src/GraphQL.Client/UriExtensions.cs index 47f5841c..54e623b7 100644 --- a/src/GraphQL.Client/UriExtensions.cs +++ b/src/GraphQL.Client/UriExtensions.cs @@ -8,7 +8,7 @@ public static class UriExtensions /// /// public static bool HasWebSocketScheme(this Uri? uri) => - !(uri is null) && + uri is not null && (uri.Scheme.Equals("wss", StringComparison.OrdinalIgnoreCase) || uri.Scheme.Equals("ws", StringComparison.OrdinalIgnoreCase)); /// @@ -33,6 +33,6 @@ public static Uri GetWebSocketUri(this Uri uri) else throw new NotSupportedException($"cannot infer websocket uri from uri scheme {uri.Scheme}"); - return new UriBuilder(uri){Scheme = webSocketScheme}.Uri; + return new UriBuilder(uri) { Scheme = webSocketScheme }.Uri; } } diff --git a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs index 1afa6b98..c85a92a3 100644 --- a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs +++ b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs @@ -18,12 +18,11 @@ internal class GraphQLHttpWebSocket : IDisposable private readonly Uri _webSocketUri; private readonly GraphQLHttpClient _client; private readonly ArraySegment _buffer; - private readonly CancellationTokenSource _internalCancellationTokenSource = new CancellationTokenSource(); + private readonly CancellationTokenSource _internalCancellationTokenSource = new(); private readonly CancellationToken _internalCancellationToken; - private readonly Subject _requestSubject = new Subject(); - private readonly Subject _exceptionSubject = new Subject(); - private readonly BehaviorSubject _stateSubject = - new BehaviorSubject(GraphQLWebsocketConnectionState.Disconnected); + private readonly Subject _requestSubject = new(); + private readonly Subject _exceptionSubject = new(); + private readonly BehaviorSubject _stateSubject = new(GraphQLWebsocketConnectionState.Disconnected); private readonly IDisposable _requestSubscription; private int _connectionAttempt = 0; @@ -32,12 +31,12 @@ internal class GraphQLHttpWebSocket : IDisposable private GraphQLHttpClientOptions Options => _client.Options; private Task _initializeWebSocketTask = Task.CompletedTask; - private readonly object _initializeLock = new object(); + private readonly object _initializeLock = new(); #if NETFRAMEWORK - private WebSocket _clientWebSocket = null; + private WebSocket _clientWebSocket; #else - private ClientWebSocket _clientWebSocket = null; + private ClientWebSocket _clientWebSocket; #endif #endregion @@ -93,7 +92,7 @@ public IObservable> CreateSubscriptionStream Observable.Create>(async observer => { - Debug.WriteLine($"Create observable thread id: {Thread.CurrentThread.ManagedThreadId}"); + Debug.WriteLine($"Create observable thread id: {Environment.CurrentManagedThreadId}"); var preprocessedRequest = await _client.Options.PreprocessRequest(request, _client).ConfigureAwait(false); var startRequest = new GraphQLWebSocketRequest @@ -123,7 +122,7 @@ public IObservable> CreateSubscriptionStream( response.MessageBytes); @@ -207,7 +206,7 @@ public IObservable> CreateSubscriptionStream> CreateSubscriptionStream, Exception>>(); + } else { - Debug.WriteLine($"Catch handler thread id: {Thread.CurrentThread.ManagedThreadId}"); + Debug.WriteLine($"Catch handler thread id: {Environment.CurrentManagedThreadId}"); return Observable.Throw, Exception>>(e); } } @@ -239,12 +240,12 @@ public IObservable> CreateSubscriptionStream {t.Item2}"); + Debug.WriteLine($"unwrap exception thread id: {Environment.CurrentManagedThreadId} => {t.Item2}"); return Observable.Throw>(t.Item2); } if (t.Item1 == null) { - Debug.WriteLine($"empty item thread id: {Thread.CurrentThread.ManagedThreadId}"); + Debug.WriteLine($"empty item thread id: {Environment.CurrentManagedThreadId}"); return Observable.Empty>(); } return Observable.Return(t.Item1); @@ -342,7 +343,7 @@ private async Task SendWebSocketRequestAsync(GraphQLWebSocketRequest reque private async Task SendWebSocketMessageAsync(GraphQLWebSocketRequest request, CancellationToken cancellationToken = default) { - var requestBytes = _client.JsonSerializer.SerializeToBytes(request); + byte[] requestBytes = _client.JsonSerializer.SerializeToBytes(request); await _clientWebSocket.SendAsync( new ArraySegment(requestBytes), WebSocketMessageType.Text, @@ -364,7 +365,9 @@ public Task InitializeWebSocket() if (_initializeWebSocketTask != null && !_initializeWebSocketTask.IsFaulted && !_initializeWebSocketTask.IsCompleted) + { return _initializeWebSocketTask; + } // if the websocket is open, return a completed task if (_clientWebSocket != null && _clientWebSocket.State == WebSocketState.Open) @@ -374,24 +377,27 @@ public Task InitializeWebSocket() _clientWebSocket?.Dispose(); #if NETFRAMEWORK - // 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"); - nativeWebSocket.Options.ClientCertificates = ((HttpClientHandler)Options.HttpMessageHandler).ClientCertificates; - nativeWebSocket.Options.UseDefaultCredentials = ((HttpClientHandler)Options.HttpMessageHandler).UseDefaultCredentials; + // 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"); + nativeWebSocket.Options.ClientCertificates = ((HttpClientHandler)Options.HttpMessageHandler).ClientCertificates; + nativeWebSocket.Options.UseDefaultCredentials = ((HttpClientHandler)Options.HttpMessageHandler).UseDefaultCredentials; Options.ConfigureWebsocketOptions(nativeWebSocket.Options); break; - case System.Net.WebSockets.Managed.ClientWebSocket managedWebSocket: - managedWebSocket.Options.AddSubProtocol("graphql-ws"); - managedWebSocket.Options.ClientCertificates = ((HttpClientHandler)Options.HttpMessageHandler).ClientCertificates; - managedWebSocket.Options.UseDefaultCredentials = ((HttpClientHandler)Options.HttpMessageHandler).UseDefaultCredentials; + + case System.Net.WebSockets.Managed.ClientWebSocket managedWebSocket: + managedWebSocket.Options.AddSubProtocol("graphql-ws"); + managedWebSocket.Options.ClientCertificates = ((HttpClientHandler)Options.HttpMessageHandler).ClientCertificates; + managedWebSocket.Options.UseDefaultCredentials = ((HttpClientHandler)Options.HttpMessageHandler).UseDefaultCredentials; break; - default: - throw new NotSupportedException($"unknown websocket type {_clientWebSocket.GetType().Name}"); - } + + default: + throw new NotSupportedException($"unknown websocket type {_clientWebSocket.GetType().Name}"); + } #else _clientWebSocket = new ClientWebSocket(); _clientWebSocket.Options.AddSubProtocol("graphql-ws"); @@ -437,7 +443,7 @@ private async Task ConnectAsync(CancellationToken token) { await BackOff().ConfigureAwait(false); _stateSubject.OnNext(GraphQLWebsocketConnectionState.Connecting); - Debug.WriteLine($"opening websocket {_clientWebSocket.GetHashCode()} (thread {Thread.CurrentThread.ManagedThreadId})"); + Debug.WriteLine($"opening websocket {_clientWebSocket.GetHashCode()} (thread {Environment.CurrentManagedThreadId})"); await _clientWebSocket.ConnectAsync(_webSocketUri, token).ConfigureAwait(false); _stateSubject.OnNext(GraphQLWebsocketConnectionState.Connected); Debug.WriteLine($"connection established on websocket {_clientWebSocket.GetHashCode()}, invoking Options.OnWebsocketConnected()"); @@ -491,14 +497,16 @@ private async Task ConnectAsync(CancellationToken token) // send connection init Debug.WriteLine($"sending connection init message"); - await SendWebSocketMessageAsync(initRequest).ConfigureAwait(false); + await SendWebSocketMessageAsync(initRequest, CancellationToken.None).ConfigureAwait(false); var response = await ackTask.ConfigureAwait(false); if (response.Type == GraphQLWebSocketMessageType.GQL_CONNECTION_ACK) + { Debug.WriteLine($"connection acknowledged: {Encoding.UTF8.GetString(response.MessageBytes)}"); + } else { - var errorPayload = Encoding.UTF8.GetString(response.MessageBytes); + string errorPayload = Encoding.UTF8.GetString(response.MessageBytes); Debug.WriteLine($"connection error received: {errorPayload}"); throw new GraphQLWebsocketConnectionException(errorPayload); } @@ -542,15 +550,15 @@ private IObservable GetMessageStream() => }; // add some debug output - var hashCode = subscription.GetHashCode(); + int hashCode = subscription.GetHashCode(); subscription.Add(Disposable.Create(() => Debug.WriteLine($"incoming message subscription {hashCode} disposed"))); Debug.WriteLine($"new incoming message subscription {hashCode} created"); return subscription; }); - private Task _receiveAsyncTask = null; - private readonly object _receiveTaskLocker = new object(); + private Task _receiveAsyncTask; + private readonly object _receiveTaskLocker = new(); /// /// wrapper method to pick up the existing request task if already running /// @@ -563,7 +571,9 @@ private Task GetReceiveTask() if (_receiveAsyncTask == null || _receiveAsyncTask.IsFaulted || _receiveAsyncTask.IsCompleted) + { _receiveAsyncTask = ReceiveWebsocketMessagesAsync(); + } } return _receiveAsyncTask; @@ -579,7 +589,7 @@ private async Task ReceiveWebsocketMessagesAsync() try { - Debug.WriteLine($"waiting for data on websocket {_clientWebSocket.GetHashCode()} (thread {Thread.CurrentThread.ManagedThreadId})..."); + Debug.WriteLine($"waiting for data on websocket {_clientWebSocket.GetHashCode()} (thread {Environment.CurrentManagedThreadId})..."); using var ms = new MemoryStream(); WebSocketReceiveResult webSocketReceiveResult = null; @@ -599,7 +609,7 @@ private async Task ReceiveWebsocketMessagesAsync() case WebSocketMessageType.Text: var response = await _client.JsonSerializer.DeserializeToWebsocketResponseWrapperAsync(ms).ConfigureAwait(false); response.MessageBytes = ms.ToArray(); - Debug.WriteLine($"{response.MessageBytes.Length} bytes received for id {response.Id} on websocket {_clientWebSocket.GetHashCode()} (thread {Thread.CurrentThread.ManagedThreadId})..."); + Debug.WriteLine($"{response.MessageBytes.Length} bytes received for id {response.Id} on websocket {_clientWebSocket.GetHashCode()} (thread {Environment.CurrentManagedThreadId})..."); return response; case WebSocketMessageType.Close: @@ -653,8 +663,7 @@ public void Complete() { lock (_completedLocker) { - if (Completion == null) - Completion = CompleteAsync(); + Completion ??= CompleteAsync(); } } @@ -664,7 +673,7 @@ public void Complete() /// Async disposal as recommended by Stephen Cleary (https://blog.stephencleary.com/2013/03/async-oop-6-disposal.html) public Task? Completion { get; private set; } - private readonly object _completedLocker = new object(); + private readonly object _completedLocker = new(); private async Task CompleteAsync() { Debug.WriteLine("disposing GraphQLHttpWebSocket..."); diff --git a/src/GraphQL.Client/Websocket/GraphQLWebsocketConnectionException.cs b/src/GraphQL.Client/Websocket/GraphQLWebsocketConnectionException.cs index ea4140fb..f9811b98 100644 --- a/src/GraphQL.Client/Websocket/GraphQLWebsocketConnectionException.cs +++ b/src/GraphQL.Client/Websocket/GraphQLWebsocketConnectionException.cs @@ -3,7 +3,7 @@ namespace GraphQL.Client.Http.Websocket; [Serializable] -public class GraphQLWebsocketConnectionException: Exception +public class GraphQLWebsocketConnectionException : Exception { public GraphQLWebsocketConnectionException() { diff --git a/src/GraphQL.Primitives/GraphQLRequest.cs b/src/GraphQL.Primitives/GraphQLRequest.cs index 7d9ee10c..59593d7b 100644 --- a/src/GraphQL.Primitives/GraphQLRequest.cs +++ b/src/GraphQL.Primitives/GraphQLRequest.cs @@ -56,7 +56,7 @@ public GraphQLRequest(string query, object? variables = null, string? operationN Extensions = extensions; } - public GraphQLRequest(GraphQLRequest other): base(other) { } + public GraphQLRequest(GraphQLRequest other) : base(other) { } /// /// Returns a value that indicates whether this instance is equal to a specified object diff --git a/tests/GraphQL.Client.Serializer.Tests/BaseSerializeNoCamelCaseTest.cs b/tests/GraphQL.Client.Serializer.Tests/BaseSerializeNoCamelCaseTest.cs index 9dcabfd7..14d77d16 100644 --- a/tests/GraphQL.Client.Serializer.Tests/BaseSerializeNoCamelCaseTest.cs +++ b/tests/GraphQL.Client.Serializer.Tests/BaseSerializeNoCamelCaseTest.cs @@ -1,4 +1,3 @@ -using System; using System.Text; using FluentAssertions; using GraphQL.Client.Abstractions; diff --git a/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs b/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs index 2021a792..ddb71471 100644 --- a/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs +++ b/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs @@ -1,10 +1,5 @@ -using System; -using System.IO; -using System.Linq; using System.Reflection; using System.Text; -using System.Threading; -using System.Threading.Tasks; using FluentAssertions; using FluentAssertions.Execution; using GraphQL.Client.Abstractions; diff --git a/tests/GraphQL.Client.Serializer.Tests/ConsistencyTests.cs b/tests/GraphQL.Client.Serializer.Tests/ConsistencyTests.cs index 3bafce7c..200c0acf 100644 --- a/tests/GraphQL.Client.Serializer.Tests/ConsistencyTests.cs +++ b/tests/GraphQL.Client.Serializer.Tests/ConsistencyTests.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using FluentAssertions; using FluentAssertions.Execution; using GraphQL.Client.Serializer.Newtonsoft; @@ -29,7 +28,7 @@ public void MapConvertersShouldBehaveConsistent() {""number"": 567.8} ] }"; - + var newtonsoftSerializer = new NewtonsoftJsonSerializer(); var systemTextJsonSerializer = new SystemTextJsonSerializer(); @@ -37,7 +36,7 @@ public void MapConvertersShouldBehaveConsistent() var systemTextJsonMap = System.Text.Json.JsonSerializer.Deserialize(json, systemTextJsonSerializer.Options); - using(new AssertionScope()) + using (new AssertionScope()) { CompareMaps(newtonsoftMap, systemTextJsonMap); } @@ -52,7 +51,7 @@ private void CompareMaps(Dictionary first, Dictionary map) + if (keyValuePair.Value is Dictionary map) CompareMaps(map, (Dictionary)second[keyValuePair.Key]); else keyValuePair.Value.Should().BeEquivalentTo(second[keyValuePair.Key]); diff --git a/tests/GraphQL.Client.Serializer.Tests/SystemTextJsonSerializerTests.cs b/tests/GraphQL.Client.Serializer.Tests/SystemTextJsonSerializerTests.cs index 38ebc2a1..c94eed8a 100644 --- a/tests/GraphQL.Client.Serializer.Tests/SystemTextJsonSerializerTests.cs +++ b/tests/GraphQL.Client.Serializer.Tests/SystemTextJsonSerializerTests.cs @@ -19,7 +19,7 @@ public class SystemTextJsonSerializeNoCamelCaseTest : BaseSerializeNoCamelCaseTe { public SystemTextJsonSerializeNoCamelCaseTest() : base( - new SystemTextJsonSerializer(new JsonSerializerOptions { Converters = { new JsonStringEnumConverter(new ConstantCaseJsonNamingPolicy(), false)}}.SetupImmutableConverter()), + new SystemTextJsonSerializer(new JsonSerializerOptions { Converters = { new JsonStringEnumConverter(new ConstantCaseJsonNamingPolicy(), false) } }.SetupImmutableConverter()), new GraphQL.SystemTextJson.GraphQLSerializer(new ErrorInfoProvider(opt => opt.ExposeData = true))) { } diff --git a/tests/GraphQL.Client.Serializer.Tests/TestData/DeserializeResponseTestData.cs b/tests/GraphQL.Client.Serializer.Tests/TestData/DeserializeResponseTestData.cs index 6b388483..53388f1f 100644 --- a/tests/GraphQL.Client.Serializer.Tests/TestData/DeserializeResponseTestData.cs +++ b/tests/GraphQL.Client.Serializer.Tests/TestData/DeserializeResponseTestData.cs @@ -1,5 +1,4 @@ using System.Collections; -using System.Collections.Generic; namespace GraphQL.Client.Serializer.Tests.TestData; @@ -119,7 +118,7 @@ public IEnumerator GetEnumerator() IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); private GraphQLResponse NewAnonymouslyTypedGraphQLResponse(T data, GraphQLError[]? errors = null, Map? extensions = null) - => new GraphQLResponse {Data = data, Errors = errors, Extensions = extensions}; + => new GraphQLResponse { Data = data, Errors = errors, Extensions = extensions }; } public class Friend diff --git a/tests/GraphQL.Client.Serializer.Tests/TestData/SerializeToBytesTestData.cs b/tests/GraphQL.Client.Serializer.Tests/TestData/SerializeToBytesTestData.cs index 977ea5af..85725616 100644 --- a/tests/GraphQL.Client.Serializer.Tests/TestData/SerializeToBytesTestData.cs +++ b/tests/GraphQL.Client.Serializer.Tests/TestData/SerializeToBytesTestData.cs @@ -1,5 +1,4 @@ using System.Collections; -using System.Collections.Generic; using GraphQL.Client.Abstractions.Websocket; namespace GraphQL.Client.Serializer.Tests.TestData; diff --git a/tests/GraphQL.Client.Serializer.Tests/TestData/SerializeToStringTestData.cs b/tests/GraphQL.Client.Serializer.Tests/TestData/SerializeToStringTestData.cs index 395b9efc..e01c6335 100644 --- a/tests/GraphQL.Client.Serializer.Tests/TestData/SerializeToStringTestData.cs +++ b/tests/GraphQL.Client.Serializer.Tests/TestData/SerializeToStringTestData.cs @@ -1,7 +1,4 @@ -using System; using System.Collections; -using System.Collections.Generic; -using System.Linq; namespace GraphQL.Client.Serializer.Tests.TestData; diff --git a/tests/GraphQL.Client.Tests.Common/Helpers/CallbackMonitor.cs b/tests/GraphQL.Client.Tests.Common/Helpers/CallbackMonitor.cs index eaf2e10c..b6399a2f 100644 --- a/tests/GraphQL.Client.Tests.Common/Helpers/CallbackMonitor.cs +++ b/tests/GraphQL.Client.Tests.Common/Helpers/CallbackMonitor.cs @@ -44,7 +44,7 @@ public void Reset() public class CallbackAssertions : ReferenceTypeAssertions, CallbackAssertions> { - public CallbackAssertions(CallbackMonitor tester): base(tester) + public CallbackAssertions(CallbackMonitor tester) : base(tester) { } protected override string Identifier => "callback"; diff --git a/tests/GraphQL.Client.Tests.Common/Helpers/ConcurrentTaskWrapper.cs b/tests/GraphQL.Client.Tests.Common/Helpers/ConcurrentTaskWrapper.cs index c49849ed..fa78d4a7 100644 --- a/tests/GraphQL.Client.Tests.Common/Helpers/ConcurrentTaskWrapper.cs +++ b/tests/GraphQL.Client.Tests.Common/Helpers/ConcurrentTaskWrapper.cs @@ -2,10 +2,10 @@ namespace GraphQL.Client.Tests.Common.Helpers; public class ConcurrentTaskWrapper { - public static ConcurrentTaskWrapper New(Func> createTask) => new ConcurrentTaskWrapper(createTask); + public static ConcurrentTaskWrapper New(Func> createTask) => new(createTask); private readonly Func _createTask; - private Task _internalTask = null; + private Task _internalTask; public ConcurrentTaskWrapper(Func createTask) { @@ -24,7 +24,7 @@ public Task Invoke() public class ConcurrentTaskWrapper { private readonly Func> _createTask; - private Task _internalTask = null; + private Task _internalTask; public ConcurrentTaskWrapper(Func> createTask) { @@ -39,11 +39,7 @@ public Task Invoke() return _internalTask = _createTask(); } - public void Start() - { - if (_internalTask == null) - _internalTask = _createTask(); - } + public void Start() => _internalTask ??= _createTask(); public Func> Invoking() => Invoke; diff --git a/tests/GraphQL.Integration.Tests/UriExtensionTests.cs b/tests/GraphQL.Integration.Tests/UriExtensionTests.cs index d218b5d8..7e374578 100644 --- a/tests/GraphQL.Integration.Tests/UriExtensionTests.cs +++ b/tests/GraphQL.Integration.Tests/UriExtensionTests.cs @@ -1,4 +1,4 @@ -using FluentAssertions; +using FluentAssertions; using GraphQL.Client.Http; using Xunit; diff --git a/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs b/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs index 35938f34..8978bc35 100644 --- a/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs +++ b/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs @@ -29,7 +29,7 @@ protected Base(ITestOutputHelper output, IntegrationServerTestFixture fixture) Fixture = fixture; } - protected static ReceivedMessage InitialMessage = new ReceivedMessage + protected static ReceivedMessage InitialMessage = new() { Content = "initial message", SentAt = DateTime.Now, @@ -42,11 +42,8 @@ public async Task InitializeAsync() // make sure the buffer always contains the same message Fixture.Server.Services.GetService().AddMessage(InitialMessage); - if (ChatClient == null) - { - // then create the chat client - ChatClient = Fixture.GetChatClient(true); - } + // then create the chat client + ChatClient ??= Fixture.GetChatClient(true); } public Task DisposeAsync() @@ -75,7 +72,7 @@ public async void CanUseWebSocketScheme() response.Errors.Should().BeNullOrEmpty(); response.Data.AddMessage.Content.Should().Be(message); } - + [Fact] public async void CanUseDedicatedWebSocketEndpoint() { @@ -88,7 +85,7 @@ public async void CanUseDedicatedWebSocketEndpoint() response.Errors.Should().BeNullOrEmpty(); response.Data.AddMessage.Content.Should().Be(message); } - + [Fact] public async void CanUseDedicatedWebSocketEndpointWithoutHttpEndpoint() { @@ -155,8 +152,7 @@ public async void CanHandleRequestErrorViaWebsocket() } }"; - private readonly GraphQLRequest _subscriptionRequest = new GraphQLRequest(SUBSCRIPTION_QUERY); - + private readonly GraphQLRequest _subscriptionRequest = new(SUBSCRIPTION_QUERY); [Fact] public async void CanCreateObservableSubscription() @@ -234,9 +230,9 @@ public async void CanReconnectWithSameObservable() Debug.WriteLine("disposing subscription..."); observer.Dispose(); // does not close the websocket connection - Debug.WriteLine($"creating new subscription from thread {Thread.CurrentThread.ManagedThreadId} ..."); + Debug.WriteLine($"creating new subscription from thread {Environment.CurrentManagedThreadId} ..."); var observer2 = observable.Observe(); - Debug.WriteLine($"waiting for payload on {Thread.CurrentThread.ManagedThreadId} ..."); + Debug.WriteLine($"waiting for payload on {Environment.CurrentManagedThreadId} ..."); await observer2.Should().PushAsync(1); observer2.RecordedMessages.Last().Data.MessageAdded.Content.Should().Be(message2); @@ -273,12 +269,12 @@ public class UserJoinedContent } - private readonly GraphQLRequest _subscriptionRequest2 = new GraphQLRequest(SUBSCRIPTION_QUERY2); + private readonly GraphQLRequest _subscriptionRequest2 = new(SUBSCRIPTION_QUERY2); [Fact] public async void CanConnectTwoSubscriptionsSimultaneously() { - var port = NetworkHelpers.GetFreeTcpPortNumber(); + int port = NetworkHelpers.GetFreeTcpPortNumber(); var callbackTester = new CallbackMonitor(); var callbackTester2 = new CallbackMonitor(); @@ -376,7 +372,7 @@ public async void CanHandleConnectionTimeout() { websocketStates.Should().ContainSingle(state => state == GraphQLWebsocketConnectionState.Disconnected); - Debug.WriteLine($"Test method thread id: {Thread.CurrentThread.ManagedThreadId}"); + Debug.WriteLine($"Test method thread id: {Environment.CurrentManagedThreadId}"); Debug.WriteLine("creating subscription stream"); var observable = ChatClient.CreateSubscriptionStream(_subscriptionRequest, errorMonitor.Invoke); diff --git a/tests/GraphQL.Primitives.Tests/JsonSerializationTests.cs b/tests/GraphQL.Primitives.Tests/JsonSerializationTests.cs index 2fb2bbc0..14917d58 100644 --- a/tests/GraphQL.Primitives.Tests/JsonSerializationTests.cs +++ b/tests/GraphQL.Primitives.Tests/JsonSerializationTests.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using System.Text.Json; using FluentAssertions; using Xunit; diff --git a/tests/GraphQL.Server.Test/Startup.cs b/tests/GraphQL.Server.Test/Startup.cs index 4dd737c5..114ce5ff 100644 --- a/tests/GraphQL.Server.Test/Startup.cs +++ b/tests/GraphQL.Server.Test/Startup.cs @@ -1,6 +1,4 @@ -using GraphQL.MicrosoftDI; using GraphQL.Server.Test.GraphQL; -using GraphQL.Server.Ui.GraphiQL; namespace GraphQL.Server.Test; diff --git a/tests/IntegrationTestServer/Startup.cs b/tests/IntegrationTestServer/Startup.cs index 65ba8a99..d2b8d427 100644 --- a/tests/IntegrationTestServer/Startup.cs +++ b/tests/IntegrationTestServer/Startup.cs @@ -2,12 +2,8 @@ using GraphQL.Client.Tests.Common; using GraphQL.Client.Tests.Common.Chat.Schema; using GraphQL.Client.Tests.Common.StarWars; -using GraphQL.MicrosoftDI; -using GraphQL.Server; using GraphQL.Server.Ui.Altair; using GraphQL.Server.Ui.GraphiQL; -using GraphQL.SystemTextJson; -using GraphQL.Types; using Microsoft.AspNetCore.Server.Kestrel.Core; namespace IntegrationTestServer; @@ -38,7 +34,7 @@ public void ConfigureServices(IServiceCollection services) { var logger = ctx.Context.RequestServices.GetRequiredService>(); logger.LogError("{Error} occurred", ctx.OriginalException.Message); - return System.Threading.Tasks.Task.CompletedTask; + return Task.CompletedTask; }) .AddErrorInfoProvider(opt => opt.ExposeExceptionDetails = Environment.IsDevelopment()) .AddSystemTextJson() @@ -52,18 +48,12 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseDeveloperExceptionPage(); } - app.UseWebSockets(); - ConfigureGraphQLSchema(app, Common.CHAT_ENDPOINT); - ConfigureGraphQLSchema(app, Common.STAR_WARS_ENDPOINT); + app.UseGraphQL(Common.CHAT_ENDPOINT); + app.UseGraphQL(Common.STAR_WARS_ENDPOINT); app.UseGraphQLGraphiQL(options: new GraphiQLOptions { GraphQLEndPoint = Common.STAR_WARS_ENDPOINT }); app.UseGraphQLAltair(options: new AltairOptions { GraphQLEndPoint = Common.CHAT_ENDPOINT }); } - - private void ConfigureGraphQLSchema(IApplicationBuilder app, string endpoint) where TSchema : Schema - { - app.UseGraphQL(endpoint); - } } From 4a3610d1f6a0f1b7505aad9de01e11ea0d7a8bba Mon Sep 17 00:00:00 2001 From: Ben Voss Date: Tue, 11 Apr 2023 09:06:35 +0100 Subject: [PATCH 424/455] Add an interface for websocket specific transport methods. (#537) * Add an interface for websocket specific transport methods. * Update src/GraphQL.Client/GraphQLHttpClient.cs Removed unnecessary interface usage Co-authored-by: Alexander Rose --------- Co-authored-by: Ben Voss Co-authored-by: Alexander Rose --- .../IGraphQLClient.cs | 19 +++++++++++++++++++ src/GraphQL.Client/GraphQLHttpClient.cs | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/GraphQL.Client.Abstractions.Websocket/IGraphQLClient.cs diff --git a/src/GraphQL.Client.Abstractions.Websocket/IGraphQLClient.cs b/src/GraphQL.Client.Abstractions.Websocket/IGraphQLClient.cs new file mode 100644 index 00000000..a8f90133 --- /dev/null +++ b/src/GraphQL.Client.Abstractions.Websocket/IGraphQLClient.cs @@ -0,0 +1,19 @@ +namespace GraphQL.Client.Abstractions.Websocket; + +public interface IGraphQLWebSocketClient : IGraphQLClient +{ + /// + /// Publishes all exceptions which occur inside the websocket receive stream (i.e. for logging purposes) + /// + IObservable WebSocketReceiveErrors { get; } + + /// + /// Publishes the websocket connection state + /// + IObservable WebsocketConnectionState { get; } + + /// + /// Explicitly opens the websocket connection. Will be closed again on disposing the last subscription. + /// + Task InitializeWebsocketConnection(); +} diff --git a/src/GraphQL.Client/GraphQLHttpClient.cs b/src/GraphQL.Client/GraphQLHttpClient.cs index e42cd678..b17527b6 100644 --- a/src/GraphQL.Client/GraphQLHttpClient.cs +++ b/src/GraphQL.Client/GraphQLHttpClient.cs @@ -6,7 +6,7 @@ namespace GraphQL.Client.Http; -public class GraphQLHttpClient : IGraphQLClient, IDisposable +public class GraphQLHttpClient : IGraphQLWebSocketClient, IDisposable { private readonly Lazy _lazyHttpWebSocket; private GraphQLHttpWebSocket GraphQlHttpWebSocket => _lazyHttpWebSocket.Value; From 8edb6fa0f49caf75f54463712082376b429385d7 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Tue, 11 Apr 2023 10:18:02 +0200 Subject: [PATCH 425/455] Fix filename of IGraphQLWebSocketClient interface (#538) fix filename of IGraphQLWebSocketClient interface --- .../{IGraphQLClient.cs => IGraphQLWebSocketClient.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/GraphQL.Client.Abstractions.Websocket/{IGraphQLClient.cs => IGraphQLWebSocketClient.cs} (100%) diff --git a/src/GraphQL.Client.Abstractions.Websocket/IGraphQLClient.cs b/src/GraphQL.Client.Abstractions.Websocket/IGraphQLWebSocketClient.cs similarity index 100% rename from src/GraphQL.Client.Abstractions.Websocket/IGraphQLClient.cs rename to src/GraphQL.Client.Abstractions.Websocket/IGraphQLWebSocketClient.cs From 19cc8f9e8799baa713eea4173d1478a868af74c8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Apr 2023 15:35:40 +0300 Subject: [PATCH 426/455] Bump Microsoft.AspNetCore.Mvc.Testing from 7.0.4 to 7.0.5 (#540) Bumps [Microsoft.AspNetCore.Mvc.Testing](https://github.com/dotnet/aspnetcore) from 7.0.4 to 7.0.5. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md) - [Commits](https://github.com/dotnet/aspnetcore/compare/v7.0.4...v7.0.5) --- updated-dependencies: - dependency-name: Microsoft.AspNetCore.Mvc.Testing dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj b/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj index 2ede20bc..d6f082fe 100644 --- a/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj +++ b/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj @@ -7,7 +7,7 @@ - + From 4430c20517e828b67572bfbed109e0c3b322e93e Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Thu, 13 Apr 2023 16:39:53 +0200 Subject: [PATCH 427/455] Support graphql-transport-ws websocket protocol (#539) * Preparing code to add graphql-transport-ws protocol without breaking previous dependant code * Added graphql-transport-ws subprotocol. Tested with Graphql Yoga server * Renamed protocols (dropping the deprecated keyword). Split GraphQLHttpWebSocket into two classes (one per protocol) that inherit from BaseGraphQLHttpWebSocket * fix formatting, do some refactoring * create unit tests for graphql-transport-ws protocol * catch json exceptions on empty close messages * fix handling of regular requests and errors * change IGraphQLWebsocketSerializer to support diffenent payload types +semver: breaking * properly hook up ping pong * implement sub protocol auto-negotiation * implement and test ping/ping * fix formatting error --------- Co-authored-by: joao-avelino --- .../GraphQLWebSocketMessageType.cs | 58 ++- .../IGraphQLWebSocketClient.cs | 26 ++ .../IGraphQLWebsocketJsonSerializer.cs | 2 +- .../NewtonsoftJsonSerializer.cs | 4 +- .../SystemTextJsonSerializer.cs | 4 +- src/GraphQL.Client/GraphQLHttpClient.cs | 33 +- .../GraphQLHttpClientOptions.cs | 6 + .../Websocket/GraphQLHttpWebSocket.cs | 413 +++++++----------- .../GraphQLTransportWSProtocolHandler.cs | 321 ++++++++++++++ .../Websocket/GraphQLWSProtocolHandler.cs | 235 ++++++++++ .../Websocket/IWebsocketProtocolHandler.cs | 23 + .../Websocket/WebSocketProtocols.cs | 20 + .../Helpers/IntegrationServerTestFixture.cs | 28 +- .../Helpers/WebHostHelpers.cs | 6 +- .../QueryAndMutationTests/Newtonsoft.cs | 4 +- .../QueryAndMutationTests/SystemTextJson.cs | 4 +- .../WebsocketTests/Base.cs | 1 - .../WebsocketTests/Newtonsoft.cs | 12 - .../NewtonsoftGraphQLTransportWs.cs | 12 + .../WebsocketTests/NewtonsoftGraphQLWs.cs | 12 + .../WebsocketTests/SystemTextJson.cs | 12 - .../SystemTextJsonAutoNegotiate.cs | 21 + .../SystemTextJsonGraphQLTransportWs.cs | 35 ++ .../WebsocketTests/SystemTextJsonGraphQLWs.cs | 40 ++ 24 files changed, 1031 insertions(+), 301 deletions(-) create mode 100644 src/GraphQL.Client/Websocket/GraphQLTransportWSProtocolHandler.cs create mode 100644 src/GraphQL.Client/Websocket/GraphQLWSProtocolHandler.cs create mode 100644 src/GraphQL.Client/Websocket/IWebsocketProtocolHandler.cs create mode 100644 src/GraphQL.Client/Websocket/WebSocketProtocols.cs delete mode 100644 tests/GraphQL.Integration.Tests/WebsocketTests/Newtonsoft.cs create mode 100644 tests/GraphQL.Integration.Tests/WebsocketTests/NewtonsoftGraphQLTransportWs.cs create mode 100644 tests/GraphQL.Integration.Tests/WebsocketTests/NewtonsoftGraphQLWs.cs delete mode 100644 tests/GraphQL.Integration.Tests/WebsocketTests/SystemTextJson.cs create mode 100644 tests/GraphQL.Integration.Tests/WebsocketTests/SystemTextJsonAutoNegotiate.cs create mode 100644 tests/GraphQL.Integration.Tests/WebsocketTests/SystemTextJsonGraphQLTransportWs.cs create mode 100644 tests/GraphQL.Integration.Tests/WebsocketTests/SystemTextJsonGraphQLWs.cs diff --git a/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketMessageType.cs b/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketMessageType.cs index 16125a71..a1628c6e 100644 --- a/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketMessageType.cs +++ b/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketMessageType.cs @@ -72,15 +72,67 @@ public static class GraphQLWebSocketMessageType public const string GQL_ERROR = "error"; // Server -> Client /// - /// Server sends this message to indicate that a GraphQL operation is done, and no more data will arrive for the - /// specific operation. + /// Server -> Client: Server sends this message to indicate that a GraphQL operation is done, and no more data will arrive + /// for the specific operation. + /// Client -> Server: "indicates that the client has stopped listening and wants to complete the subscription. No further + /// events, relevant to the original subscription, should be sent through. Even if the client sent a Complete message for + /// a single-result-operation before it resolved, the result should not be sent through once it does." + /// Replaces the GQL_STOP in graphql-transport-ws /// id: string : operation ID of the operation that completed /// - public const string GQL_COMPLETE = "complete"; // Server -> Client + public const string GQL_COMPLETE = "complete"; // Server -> Client and Client -> Server /// /// Client sends this message in order to stop a running GraphQL operation execution (for example: unsubscribe) /// id: string : operation id /// public const string GQL_STOP = "stop"; // Client -> Server + + + // Additional types for graphql-transport-ws, as described in https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md + + /// + /// Bidirectional. "Useful for detecting failed connections, displaying latency metrics or other types of network probing. + /// A Pong must be sent in response from the receiving party as soon as possible. The Ping message can be sent at any time + /// within the established socket. The optional payload field can be used to transfer additional details about the ping." + /// payload: Object: ping details + /// + public const string GQL_PING = "ping"; // Bidirectional + + /// + /// Bidirectional. "The response to the Ping message. Must be sent as soon as the Ping message is received. + /// The Pong message can be sent at any time within the established socket. Furthermore, the Pong message + /// may even be sent unsolicited as an unidirectional heartbeat. The optional payload field can be used to + /// transfer additional details about the pong." + /// payload: Object: pong details + /// + public const string GQL_PONG = "pong"; // Bidirectional + + /// + /// Client-> Server. "Requests an operation specified in the message payload. This message provides a unique + /// ID field to connect published messages to the operation requested by this message. If there is already an + /// active subscriber for an operation matching the provided ID, regardless of the operation type, the server + /// must close the socket immediately with the event 4409: Subscriber for *unique-operation-id* already exists. + /// The server needs only keep track of IDs for as long as the subscription is active. Once a client completes + /// an operation, it is free to re-use that ID." + /// id: string : operation id + /// payload: Object: + /// operationName : string : subscribe + /// query : string : the subscription query + /// variables : Dictionary(string, string) : a dictionary with variables and their values + /// extensions : Dictionary(string, string) : a dictionary of extensions + /// + public const string GQL_SUBSCRIBE = "subscribe"; // Client -> Server + + + /// + /// Server -> Client. "Operation execution result(s) from the source stream created by the binding Subscribe + /// message. After all results have been emitted, the Complete message will follow indicating stream + /// completion." + /// id: string : operation id + /// payload: Object: ExecutionResult + /// + + public const string GQL_NEXT = "next"; // Server -> Client + } diff --git a/src/GraphQL.Client.Abstractions.Websocket/IGraphQLWebSocketClient.cs b/src/GraphQL.Client.Abstractions.Websocket/IGraphQLWebSocketClient.cs index a8f90133..63c92362 100644 --- a/src/GraphQL.Client.Abstractions.Websocket/IGraphQLWebSocketClient.cs +++ b/src/GraphQL.Client.Abstractions.Websocket/IGraphQLWebSocketClient.cs @@ -2,6 +2,11 @@ namespace GraphQL.Client.Abstractions.Websocket; public interface IGraphQLWebSocketClient : IGraphQLClient { + /// + /// The negotiated websocket sub-protocol. Will be while no websocket connection is established. + /// + string? WebSocketSubProtocol { get; } + /// /// Publishes all exceptions which occur inside the websocket receive stream (i.e. for logging purposes) /// @@ -16,4 +21,25 @@ public interface IGraphQLWebSocketClient : IGraphQLClient /// Explicitly opens the websocket connection. Will be closed again on disposing the last subscription. /// Task InitializeWebsocketConnection(); + + /// + /// Publishes the payload of all received pong messages (which may be ). Subscribing initiates the websocket connection.
+ /// Ping/Pong is only supported when using the "graphql-transport-ws" websocket sub-protocol. + ///
+ /// the negotiated websocket sub-protocol does not support ping/pong + IObservable PongStream { get; } + + /// + /// Sends a ping to the server.
+ /// Ping/Pong is only supported when using the "graphql-transport-ws" websocket sub-protocol. + ///
+ /// the negotiated websocket sub-protocol does not support ping/pong + Task SendPingAsync(object? payload); + + /// + /// Sends a pong to the server. This can be used for keep-alive scenarios (the client will automatically respond to pings received from the server).
+ /// Ping/Pong is only supported when using the "graphql-transport-ws" websocket sub-protocol. + ///
+ /// the negotiated websocket sub-protocol does not support ping/pong + Task SendPongAsync(object? payload); } diff --git a/src/GraphQL.Client.Abstractions.Websocket/IGraphQLWebsocketJsonSerializer.cs b/src/GraphQL.Client.Abstractions.Websocket/IGraphQLWebsocketJsonSerializer.cs index f7c4a2bf..e171fe8a 100644 --- a/src/GraphQL.Client.Abstractions.Websocket/IGraphQLWebsocketJsonSerializer.cs +++ b/src/GraphQL.Client.Abstractions.Websocket/IGraphQLWebsocketJsonSerializer.cs @@ -10,5 +10,5 @@ public interface IGraphQLWebsocketJsonSerializer : IGraphQLJsonSerializer Task DeserializeToWebsocketResponseWrapperAsync(Stream stream); - GraphQLWebSocketResponse> DeserializeToWebsocketResponse(byte[] bytes); + GraphQLWebSocketResponse DeserializeToWebsocketResponse(byte[] bytes); } diff --git a/src/GraphQL.Client.Serializer.Newtonsoft/NewtonsoftJsonSerializer.cs b/src/GraphQL.Client.Serializer.Newtonsoft/NewtonsoftJsonSerializer.cs index 0cfd14b1..cc1d78bc 100644 --- a/src/GraphQL.Client.Serializer.Newtonsoft/NewtonsoftJsonSerializer.cs +++ b/src/GraphQL.Client.Serializer.Newtonsoft/NewtonsoftJsonSerializer.cs @@ -40,8 +40,8 @@ public byte[] SerializeToBytes(GraphQLWebSocketRequest request) public Task DeserializeToWebsocketResponseWrapperAsync(Stream stream) => DeserializeFromUtf8Stream(stream); - public GraphQLWebSocketResponse> DeserializeToWebsocketResponse(byte[] bytes) => - JsonConvert.DeserializeObject>>(Encoding.UTF8.GetString(bytes), + public GraphQLWebSocketResponse DeserializeToWebsocketResponse(byte[] bytes) => + JsonConvert.DeserializeObject>(Encoding.UTF8.GetString(bytes), JsonSerializerSettings); public Task> DeserializeFromUtf8StreamAsync(Stream stream, CancellationToken cancellationToken) => DeserializeFromUtf8Stream>(stream); diff --git a/src/GraphQL.Client.Serializer.SystemTextJson/SystemTextJsonSerializer.cs b/src/GraphQL.Client.Serializer.SystemTextJson/SystemTextJsonSerializer.cs index 232fa421..2488358d 100644 --- a/src/GraphQL.Client.Serializer.SystemTextJson/SystemTextJsonSerializer.cs +++ b/src/GraphQL.Client.Serializer.SystemTextJson/SystemTextJsonSerializer.cs @@ -42,7 +42,7 @@ private void ConfigureMandatorySerializerOptions() public Task DeserializeToWebsocketResponseWrapperAsync(Stream stream) => JsonSerializer.DeserializeAsync(stream, Options).AsTask(); - public GraphQLWebSocketResponse> DeserializeToWebsocketResponse(byte[] bytes) => - JsonSerializer.Deserialize>>(new ReadOnlySpan(bytes), + public GraphQLWebSocketResponse DeserializeToWebsocketResponse(byte[] bytes) => + JsonSerializer.Deserialize>(new ReadOnlySpan(bytes), Options); } diff --git a/src/GraphQL.Client/GraphQLHttpClient.cs b/src/GraphQL.Client/GraphQLHttpClient.cs index b17527b6..d634fe2e 100644 --- a/src/GraphQL.Client/GraphQLHttpClient.cs +++ b/src/GraphQL.Client/GraphQLHttpClient.cs @@ -30,16 +30,18 @@ public class GraphQLHttpClient : IGraphQLWebSocketClient, IDisposable ///
public GraphQLHttpClientOptions Options { get; } - /// - /// Publishes all exceptions which occur inside the websocket receive stream (i.e. for logging purposes) - /// + /// public IObservable WebSocketReceiveErrors => GraphQlHttpWebSocket.ReceiveErrors; - /// - /// the websocket connection state - /// + /// + public string? WebSocketSubProtocol => GraphQlHttpWebSocket.WebsocketProtocol; + + /// public IObservable WebsocketConnectionState => GraphQlHttpWebSocket.ConnectionState; + /// + public IObservable PongStream => GraphQlHttpWebSocket.GetPongStream(); + #region Constructors public GraphQLHttpClient(string endPoint, IGraphQLWebsocketJsonSerializer serializer) @@ -78,7 +80,7 @@ public GraphQLHttpClient(GraphQLHttpClientOptions options, IGraphQLWebsocketJson public async Task> SendQueryAsync(GraphQLRequest request, CancellationToken cancellationToken = default) { return Options.UseWebSocketForQueriesAndMutations || Options.WebSocketEndPoint is not null && Options.EndPoint is null || Options.EndPoint.HasWebSocketScheme() - ? await GraphQlHttpWebSocket.SendRequest(request, cancellationToken).ConfigureAwait(false) + ? await GraphQlHttpWebSocket.SendRequestAsync(request, cancellationToken).ConfigureAwait(false) : await SendHttpRequestAsync(request, cancellationToken).ConfigureAwait(false); } @@ -103,12 +105,15 @@ public IObservable> CreateSubscriptionStream - /// Explicitly opens the websocket connection. Will be closed again on disposing the last subscription. - ///
- /// + /// public Task InitializeWebsocketConnection() => GraphQlHttpWebSocket.InitializeWebSocket(); + /// + public Task SendPingAsync(object? payload) => GraphQlHttpWebSocket.SendPingAsync(payload); + + /// + public Task SendPongAsync(object? payload) => GraphQlHttpWebSocket.SendPongAsync(payload); + #region Private Methods private async Task> SendHttpRequestAsync(GraphQLRequest request, CancellationToken cancellationToken = default) @@ -143,9 +148,9 @@ private GraphQLHttpWebSocket CreateGraphQLHttpWebSocket() throw new InvalidOperationException("no endpoint configured"); var webSocketEndpoint = Options.WebSocketEndPoint ?? Options.EndPoint.GetWebSocketUri(); - return webSocketEndpoint.HasWebSocketScheme() - ? new GraphQLHttpWebSocket(webSocketEndpoint, this) - : throw new InvalidOperationException($"uri \"{webSocketEndpoint}\" is not a websocket endpoint"); + return !webSocketEndpoint.HasWebSocketScheme() + ? throw new InvalidOperationException($"uri \"{webSocketEndpoint}\" is not a websocket endpoint") + : new GraphQLHttpWebSocket(webSocketEndpoint, this); } #endregion diff --git a/src/GraphQL.Client/GraphQLHttpClientOptions.cs b/src/GraphQL.Client/GraphQLHttpClientOptions.cs index 22b5ddb2..74de6441 100644 --- a/src/GraphQL.Client/GraphQLHttpClientOptions.cs +++ b/src/GraphQL.Client/GraphQLHttpClientOptions.cs @@ -1,6 +1,7 @@ using System.Net; using System.Net.Http.Headers; using System.Net.WebSockets; +using GraphQL.Client.Http.Websocket; namespace GraphQL.Client.Http; @@ -19,6 +20,11 @@ public class GraphQLHttpClientOptions /// public Uri? WebSocketEndPoint { get; set; } + /// + /// The GraphQL websocket protocol to be used. Defaults to the older "graphql-ws" protocol to not break old code. + /// + public string? WebSocketProtocol { get; set; } = WebSocketProtocols.AUTO_NEGOTIATE; + /// /// The that is going to be used /// diff --git a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs index c85a92a3..32925567 100644 --- a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs +++ b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs @@ -5,38 +5,40 @@ using System.Reactive.Linq; using System.Reactive.Subjects; using System.Reactive.Threading.Tasks; -using System.Text; using GraphQL.Client.Abstractions.Websocket; namespace GraphQL.Client.Http.Websocket; + internal class GraphQLHttpWebSocket : IDisposable { #region Private fields - private readonly Uri _webSocketUri; - private readonly GraphQLHttpClient _client; + protected readonly Uri _webSocketUri; + protected readonly GraphQLHttpClient _client; private readonly ArraySegment _buffer; private readonly CancellationTokenSource _internalCancellationTokenSource = new(); - private readonly CancellationToken _internalCancellationToken; + protected readonly CancellationToken _internalCancellationToken; private readonly Subject _requestSubject = new(); - private readonly Subject _exceptionSubject = new(); - private readonly BehaviorSubject _stateSubject = new(GraphQLWebsocketConnectionState.Disconnected); + protected readonly Subject _exceptionSubject = new(); + protected readonly BehaviorSubject _stateSubject = new(GraphQLWebsocketConnectionState.Disconnected); private readonly IDisposable _requestSubscription; - private int _connectionAttempt = 0; - private IConnectableObservable _incomingMessages; - private IDisposable _incomingMessagesConnection; - private GraphQLHttpClientOptions Options => _client.Options; + protected int _connectionAttempt = 0; + protected IConnectableObservable _incomingMessages; + protected IDisposable _incomingMessagesConnection; + private IWebsocketProtocolHandler? _websocketProtocolHandler; + protected GraphQLHttpClientOptions Options => _client.Options; private Task _initializeWebSocketTask = Task.CompletedTask; private readonly object _initializeLock = new(); + #if NETFRAMEWORK - private WebSocket _clientWebSocket; + protected WebSocket? _clientWebSocket = null; #else - private ClientWebSocket _clientWebSocket; + protected ClientWebSocket? _clientWebSocket = null; #endif #endregion @@ -63,6 +65,14 @@ internal class GraphQLHttpWebSocket : IDisposable /// public IObservable IncomingMessageStream { get; } + /// + /// The websocket protocol used for subscriptions or full-websocket connections + /// + public string? WebsocketProtocol => _websocketProtocolHandler?.WebsocketProtocol; + + + public IObservable Pongs => null; + #endregion public GraphQLHttpWebSocket(Uri webSocketUri, GraphQLHttpClient client) @@ -77,10 +87,74 @@ public GraphQLHttpWebSocket(Uri webSocketUri, GraphQLHttpClient client) .Select(request => Observable.FromAsync(() => SendWebSocketRequestAsync(request))) .Concat() .Subscribe(); + } + /// + /// Returns the pong message stream. Subscribing initiates the websocket connection if not already established. + /// + /// + public IObservable GetPongStream() => + Observable.Defer(async () => + { + if (_websocketProtocolHandler is null) + { + await InitializeWebSocket().ConfigureAwait(false); + } + + return _websocketProtocolHandler.CreatePongObservable(); + }) + // complete sequence on OperationCanceledException, this is triggered by the cancellation token + .Catch(exception => + Observable.Empty()); + #region Send requests + /// + public async Task SendPingAsync(object? payload) + { + if (_websocketProtocolHandler is null) + { + await InitializeWebSocket().ConfigureAwait(false); + } + + await _websocketProtocolHandler.SendPingAsync(payload); + } + + /// + public async Task SendPongAsync(object? payload) + { + if (_websocketProtocolHandler is null) + { + await InitializeWebSocket().ConfigureAwait(false); + } + + await _websocketProtocolHandler.SendPongAsync(payload); + } + + /// + /// Send a regular GraphQL request (query, mutation) via websocket + /// + /// the response type + /// the to send + /// the token to cancel the request + /// + public async Task> SendRequestAsync(GraphQLRequest request, + CancellationToken cancellationToken = default) + { + if (_websocketProtocolHandler is null) + { + await InitializeWebSocket().ConfigureAwait(false); + } + + return await _websocketProtocolHandler.CreateGraphQLRequestObservable(request) + // complete sequence on OperationCanceledException, this is triggered by the cancellation token + .Catch, OperationCanceledException>(exception => + Observable.Empty>()) + .FirstAsync() + .ToTask(cancellationToken); + } + /// /// Create a new subscription stream /// @@ -89,109 +163,14 @@ public GraphQLHttpWebSocket(Uri webSocketUri, GraphQLHttpClient client) /// Optional: exception handler for handling exceptions within the receive pipeline /// a which represents the subscription public IObservable> CreateSubscriptionStream(GraphQLRequest request, Action? exceptionHandler = null) => - Observable.Defer(() => - Observable.Create>(async observer => + Observable.Defer(async () => + { + if (_websocketProtocolHandler is null) { - Debug.WriteLine($"Create observable thread id: {Environment.CurrentManagedThreadId}"); - var preprocessedRequest = await _client.Options.PreprocessRequest(request, _client).ConfigureAwait(false); - - var startRequest = new GraphQLWebSocketRequest - { - Id = Guid.NewGuid().ToString("N"), - Type = GraphQLWebSocketMessageType.GQL_START, - Payload = preprocessedRequest - }; - var stopRequest = new GraphQLWebSocketRequest - { - Id = startRequest.Id, - Type = GraphQLWebSocketMessageType.GQL_STOP - }; - - var observable = Observable.Create>(o => - IncomingMessageStream - // ignore null values and messages for other requests - .Where(response => response != null && response.Id == startRequest.Id) - .Subscribe(response => - { - // terminate the sequence when a 'complete' message is received - if (response.Type == GraphQLWebSocketMessageType.GQL_COMPLETE) - { - Debug.WriteLine($"received 'complete' message on subscription {startRequest.Id}"); - o.OnCompleted(); - return; - } - - // post the GraphQLResponse to the stream (even if a GraphQL error occurred) - Debug.WriteLine($"received payload on subscription {startRequest.Id} (thread {Environment.CurrentManagedThreadId})"); - var typedResponse = - _client.JsonSerializer.DeserializeToWebsocketResponse( - response.MessageBytes); - Debug.WriteLine($"payload => {System.Text.Encoding.UTF8.GetString(response.MessageBytes)}"); - o.OnNext(typedResponse.Payload); - - // in case of a GraphQL error, terminate the sequence after the response has been posted - if (response.Type == GraphQLWebSocketMessageType.GQL_ERROR) - { - Debug.WriteLine($"terminating subscription {startRequest.Id} because of a GraphQL error"); - o.OnCompleted(); - } - }, - e => - { - Debug.WriteLine($"response stream for subscription {startRequest.Id} failed: {e}"); - o.OnError(e); - }, - () => - { - Debug.WriteLine($"response stream for subscription {startRequest.Id} completed"); - o.OnCompleted(); - }) - ); - - try - { - // initialize websocket (completes immediately if socket is already open) - await InitializeWebSocket().ConfigureAwait(false); - } - catch (Exception e) - { - // subscribe observer to failed observable - return Observable.Throw>(e).Subscribe(observer); - } - - var disposable = new CompositeDisposable( - observable.Subscribe(observer), - Disposable.Create(async () => - { - Debug.WriteLine($"disposing subscription {startRequest.Id}, websocket state is '{WebSocketState}'"); - // only try to send close request on open websocket - if (WebSocketState != WebSocketState.Open) - return; - - try - { - Debug.WriteLine($"sending stop message on subscription {startRequest.Id}"); - await QueueWebSocketRequest(stopRequest).ConfigureAwait(false); - } - // do not break on disposing - catch (OperationCanceledException) { } - }) - ); - - Debug.WriteLine($"sending start message on subscription {startRequest.Id}"); - // send subscription request - try - { - await QueueWebSocketRequest(startRequest).ConfigureAwait(false); - } - catch (Exception e) - { - Debug.WriteLine(e); - throw; - } - - return disposable; - })) + await InitializeWebSocket().ConfigureAwait(false); + } + return _websocketProtocolHandler?.CreateSubscriptionObservable(request); + }) // complete sequence on OperationCanceledException, this is triggered by the cancellation token .Catch, OperationCanceledException>(exception => Observable.Empty>()) @@ -206,7 +185,7 @@ public IObservable> CreateSubscriptionStream> CreateSubscriptionStream, Exception>>(); - } else { - Debug.WriteLine($"Catch handler thread id: {Environment.CurrentManagedThreadId}"); + Debug.WriteLine($"Catch handler thread id: {Thread.CurrentThread.ManagedThreadId}"); return Observable.Throw, Exception>>(e); } } @@ -240,87 +217,24 @@ public IObservable> CreateSubscriptionStream {t.Item2}"); + Debug.WriteLine($"unwrap exception thread id: {Thread.CurrentThread.ManagedThreadId} => {t.Item2}"); return Observable.Throw>(t.Item2); } if (t.Item1 == null) { - Debug.WriteLine($"empty item thread id: {Environment.CurrentManagedThreadId}"); + Debug.WriteLine($"empty item thread id: {Thread.CurrentThread.ManagedThreadId}"); return Observable.Empty>(); } return Observable.Return(t.Item1); }); - /// - /// Send a regular GraphQL request (query, mutation) via websocket - /// - /// the response type - /// the to send - /// the token to cancel the request - /// - public Task> SendRequest(GraphQLRequest request, CancellationToken cancellationToken = default) => - Observable.Create>(async observer => - { - var preprocessedRequest = await _client.Options.PreprocessRequest(request, _client).ConfigureAwait(false); - var websocketRequest = new GraphQLWebSocketRequest - { - Id = Guid.NewGuid().ToString("N"), - Type = GraphQLWebSocketMessageType.GQL_START, - Payload = preprocessedRequest - }; - var observable = IncomingMessageStream - .Where(response => response != null && response.Id == websocketRequest.Id) - .TakeUntil(response => response.Type == GraphQLWebSocketMessageType.GQL_COMPLETE) - .Select(response => - { - Debug.WriteLine($"received response for request {websocketRequest.Id}"); - var typedResponse = - _client.JsonSerializer.DeserializeToWebsocketResponse( - response.MessageBytes); - return typedResponse.Payload; - }); - - try - { - // initialize websocket (completes immediately if socket is already open) - await 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 QueueWebSocketRequest(websocketRequest).ConfigureAwait(false); - } - catch (Exception e) - { - Debug.WriteLine(e); - throw; - } - return disposable; - }) - // complete sequence on OperationCanceledException, this is triggered by the cancellation token - .Catch, OperationCanceledException>(exception => - Observable.Empty>()) - .FirstAsync() - .ToTask(cancellationToken); - - private Task QueueWebSocketRequest(GraphQLWebSocketRequest request) + protected Task QueueWebSocketRequest(GraphQLWebSocketRequest request) { _requestSubject.OnNext(request); return request.SendTask(); } - private async Task SendWebSocketRequestAsync(GraphQLWebSocketRequest request) + protected async Task SendWebSocketRequestAsync(GraphQLWebSocketRequest request) { try { @@ -341,9 +255,9 @@ private async Task SendWebSocketRequestAsync(GraphQLWebSocketRequest reque return Unit.Default; } - private async Task SendWebSocketMessageAsync(GraphQLWebSocketRequest request, CancellationToken cancellationToken = default) + protected async Task SendWebSocketMessageAsync(GraphQLWebSocketRequest request, CancellationToken cancellationToken = default) { - byte[] requestBytes = _client.JsonSerializer.SerializeToBytes(request); + var requestBytes = _client.JsonSerializer.SerializeToBytes(request); await _clientWebSocket.SendAsync( new ArraySegment(requestBytes), WebSocketMessageType.Text, @@ -365,9 +279,7 @@ public Task InitializeWebSocket() if (_initializeWebSocketTask != null && !_initializeWebSocketTask.IsFaulted && !_initializeWebSocketTask.IsCompleted) - { return _initializeWebSocketTask; - } // if the websocket is open, return a completed task if (_clientWebSocket != null && _clientWebSocket.State == WebSocketState.Open) @@ -383,24 +295,22 @@ public Task InitializeWebSocket() switch (_clientWebSocket) { case ClientWebSocket nativeWebSocket: - nativeWebSocket.Options.AddSubProtocol("graphql-ws"); + ConfigureWebSocketSubProtocols(nativeWebSocket.Options.AddSubProtocol); nativeWebSocket.Options.ClientCertificates = ((HttpClientHandler)Options.HttpMessageHandler).ClientCertificates; nativeWebSocket.Options.UseDefaultCredentials = ((HttpClientHandler)Options.HttpMessageHandler).UseDefaultCredentials; Options.ConfigureWebsocketOptions(nativeWebSocket.Options); break; - case System.Net.WebSockets.Managed.ClientWebSocket managedWebSocket: - managedWebSocket.Options.AddSubProtocol("graphql-ws"); + ConfigureWebSocketSubProtocols(managedWebSocket.Options.AddSubProtocol); managedWebSocket.Options.ClientCertificates = ((HttpClientHandler)Options.HttpMessageHandler).ClientCertificates; managedWebSocket.Options.UseDefaultCredentials = ((HttpClientHandler)Options.HttpMessageHandler).UseDefaultCredentials; break; - default: throw new NotSupportedException($"unknown websocket type {_clientWebSocket.GetType().Name}"); } #else _clientWebSocket = new ClientWebSocket(); - _clientWebSocket.Options.AddSubProtocol("graphql-ws"); + ConfigureWebSocketSubProtocols(_clientWebSocket.Options.AddSubProtocol); // the following properties are not supported in Blazor WebAssembly and throw a PlatformNotSupportedException error when accessed try @@ -432,25 +342,53 @@ public Task InitializeWebSocket() } Options.ConfigureWebsocketOptions(_clientWebSocket.Options); + #endif return _initializeWebSocketTask = ConnectAsync(_internalCancellationToken); } } - private async Task ConnectAsync(CancellationToken token) + public void ConfigureWebSocketSubProtocols(Action addSubProtocol) + { + if (_client.Options.WebSocketProtocol is null) + { + foreach (string protocol in WebSocketProtocols.GetSupportedWebSocketProtocols()) + { + addSubProtocol(protocol); + } + } + else + addSubProtocol(_client.Options.WebSocketProtocol); + } + + + protected async Task ConnectAsync(CancellationToken token) { try { + //Client sends a WebSocket handshake request with the sub-protocol: graphql-transport-ws await BackOff().ConfigureAwait(false); _stateSubject.OnNext(GraphQLWebsocketConnectionState.Connecting); - Debug.WriteLine($"opening websocket {_clientWebSocket.GetHashCode()} (thread {Environment.CurrentManagedThreadId})"); + Debug.WriteLine($"opening websocket {_clientWebSocket.GetHashCode()} (thread {Thread.CurrentThread.ManagedThreadId})"); await _clientWebSocket.ConnectAsync(_webSocketUri, token).ConfigureAwait(false); + // check negotiated sub protocol and create matching instance of IWebsocketProtocolHandler + _websocketProtocolHandler = _clientWebSocket.SubProtocol switch + { + WebSocketProtocols.GRAPHQL_WS + => new GraphQLWSProtocolHandler(this, _client, QueueWebSocketRequest, SendWebSocketMessageAsync), + WebSocketProtocols.GRAPHQL_TRANSPORT_WS + => new GraphQLTransportWSProtocolHandler(this, _client, QueueWebSocketRequest, SendWebSocketMessageAsync), + _ => throw new NotSupportedException( + $"negotiated websocket protocol \"{_clientWebSocket.SubProtocol}\" not supported") + }; _stateSubject.OnNext(GraphQLWebsocketConnectionState.Connected); Debug.WriteLine($"connection established on websocket {_clientWebSocket.GetHashCode()}, invoking Options.OnWebsocketConnected()"); await (Options.OnWebsocketConnected?.Invoke(_client) ?? Task.CompletedTask).ConfigureAwait(false); Debug.WriteLine($"invoking Options.OnWebsocketConnected() on websocket {_clientWebSocket.GetHashCode()}"); _connectionAttempt = 1; + // Client immediately dispatches a ConnectionInit message optionally providing a payload as agreed with the server + // create receiving observable _incomingMessages = Observable .Defer(() => GetReceiveTask().ToObservable()) @@ -479,37 +417,10 @@ private async Task ConnectAsync(CancellationToken token) var connection = _incomingMessages.Connect(); Debug.WriteLine($"new incoming message stream {_incomingMessages.GetHashCode()} created"); - _incomingMessagesConnection = new CompositeDisposable(maintenanceSubscription, connection); - - var initRequest = new GraphQLWebSocketRequest - { - Type = GraphQLWebSocketMessageType.GQL_CONNECTION_INIT, - Payload = Options.ConfigureWebSocketConnectionInitPayload(Options) - }; - - // setup task to await connection_ack message - var ackTask = _incomingMessages - .Where(response => response != null) - .TakeUntil(response => response.Type == GraphQLWebSocketMessageType.GQL_CONNECTION_ACK || - response.Type == GraphQLWebSocketMessageType.GQL_CONNECTION_ERROR) - .LastAsync() - .ToTask(); + var closeConnectionDisposable = new CompositeDisposable(); + _incomingMessagesConnection = new CompositeDisposable(connection, maintenanceSubscription, closeConnectionDisposable); - // send connection init - Debug.WriteLine($"sending connection init message"); - await SendWebSocketMessageAsync(initRequest, CancellationToken.None).ConfigureAwait(false); - var response = await ackTask.ConfigureAwait(false); - - if (response.Type == GraphQLWebSocketMessageType.GQL_CONNECTION_ACK) - { - Debug.WriteLine($"connection acknowledged: {Encoding.UTF8.GetString(response.MessageBytes)}"); - } - else - { - string errorPayload = Encoding.UTF8.GetString(response.MessageBytes); - Debug.WriteLine($"connection error received: {errorPayload}"); - throw new GraphQLWebsocketConnectionException(errorPayload); - } + await _websocketProtocolHandler.InitializeConnectionAsync(_incomingMessages, closeConnectionDisposable).ConfigureAwait(false); } catch (Exception e) { @@ -520,11 +431,12 @@ private async Task ConnectAsync(CancellationToken token) } } + /// /// delay the next connection attempt using /// /// - private Task BackOff() + protected Task BackOff() { _connectionAttempt++; @@ -536,7 +448,7 @@ private Task BackOff() return Task.Delay(delay, _internalCancellationToken); } - private IObservable GetMessageStream() => + protected IObservable GetMessageStream() => Observable.Create(async observer => { // make sure the websocket is connected @@ -550,20 +462,20 @@ private IObservable GetMessageStream() => }; // add some debug output - int hashCode = subscription.GetHashCode(); + var hashCode = subscription.GetHashCode(); subscription.Add(Disposable.Create(() => Debug.WriteLine($"incoming message subscription {hashCode} disposed"))); Debug.WriteLine($"new incoming message subscription {hashCode} created"); return subscription; }); - private Task _receiveAsyncTask; - private readonly object _receiveTaskLocker = new(); + protected Task _receiveAsyncTask = null; + protected readonly object _receiveTaskLocker = new(); /// /// wrapper method to pick up the existing request task if already running /// /// - private Task GetReceiveTask() + protected Task GetReceiveTask() { lock (_receiveTaskLocker) { @@ -571,9 +483,7 @@ private Task GetReceiveTask() if (_receiveAsyncTask == null || _receiveAsyncTask.IsFaulted || _receiveAsyncTask.IsCompleted) - { _receiveAsyncTask = ReceiveWebsocketMessagesAsync(); - } } return _receiveAsyncTask; @@ -583,13 +493,13 @@ private Task GetReceiveTask() /// read a single message from the websocket /// /// - private async Task ReceiveWebsocketMessagesAsync() + protected async Task ReceiveWebsocketMessagesAsync() { _internalCancellationToken.ThrowIfCancellationRequested(); try { - Debug.WriteLine($"waiting for data on websocket {_clientWebSocket.GetHashCode()} (thread {Environment.CurrentManagedThreadId})..."); + Debug.WriteLine($"waiting for data on websocket {_clientWebSocket.GetHashCode()} (thread {Thread.CurrentThread.ManagedThreadId})..."); using var ms = new MemoryStream(); WebSocketReceiveResult webSocketReceiveResult = null; @@ -609,12 +519,21 @@ private async Task ReceiveWebsocketMessagesAsync() case WebSocketMessageType.Text: var response = await _client.JsonSerializer.DeserializeToWebsocketResponseWrapperAsync(ms).ConfigureAwait(false); response.MessageBytes = ms.ToArray(); - Debug.WriteLine($"{response.MessageBytes.Length} bytes received for id {response.Id} on websocket {_clientWebSocket.GetHashCode()} (thread {Environment.CurrentManagedThreadId})..."); + Debug.WriteLine($"{response.MessageBytes.Length} bytes received for id {response.Id} on websocket {_clientWebSocket.GetHashCode()} (thread {Thread.CurrentThread.ManagedThreadId})..."); return response; case WebSocketMessageType.Close: - var closeResponse = await _client.JsonSerializer.DeserializeToWebsocketResponseWrapperAsync(ms).ConfigureAwait(false); - closeResponse.MessageBytes = ms.ToArray(); + WebsocketMessageWrapper closeResponse = null; + try + { + closeResponse = await _client.JsonSerializer.DeserializeToWebsocketResponseWrapperAsync(ms).ConfigureAwait(false); + } + catch (Exception e) + { + Console.WriteLine(e); + } + if (closeResponse != null) + closeResponse.MessageBytes = ms.ToArray(); Debug.WriteLine($"Connection closed by the server."); throw new Exception("Connection closed by the server."); @@ -630,7 +549,7 @@ private async Task ReceiveWebsocketMessagesAsync() } } - private async Task CloseAsync() + protected async Task CloseAsync() { if (_clientWebSocket == null) return; @@ -644,12 +563,16 @@ private async Task CloseAsync() return; } - Debug.WriteLine($"send \"connection_terminate\" message"); - await SendWebSocketMessageAsync(new GraphQLWebSocketRequest { Type = GraphQLWebSocketMessageType.GQL_CONNECTION_TERMINATE }).ConfigureAwait(false); + if (_websocketProtocolHandler is not null) + { + Debug.WriteLine($"send \"connection_terminate\" message"); + await _websocketProtocolHandler.SendCloseConnectionRequestAsync().ConfigureAwait(false); + } Debug.WriteLine($"closing websocket {_clientWebSocket.GetHashCode()}"); await _clientWebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None).ConfigureAwait(false); _stateSubject.OnNext(GraphQLWebsocketConnectionState.Disconnected); + } #region IDisposable @@ -671,10 +594,10 @@ public void Complete() /// Task to await the completion (a.k.a. disposal) of this websocket. /// /// Async disposal as recommended by Stephen Cleary (https://blog.stephencleary.com/2013/03/async-oop-6-disposal.html) - public Task? Completion { get; private set; } + public Task? Completion { get; protected set; } - private readonly object _completedLocker = new(); - private async Task CompleteAsync() + protected readonly object _completedLocker = new(); + protected async Task CompleteAsync() { Debug.WriteLine("disposing GraphQLHttpWebSocket..."); diff --git a/src/GraphQL.Client/Websocket/GraphQLTransportWSProtocolHandler.cs b/src/GraphQL.Client/Websocket/GraphQLTransportWSProtocolHandler.cs new file mode 100644 index 00000000..c78581b1 --- /dev/null +++ b/src/GraphQL.Client/Websocket/GraphQLTransportWSProtocolHandler.cs @@ -0,0 +1,321 @@ +using System.Diagnostics; +using System.Net.WebSockets; +using System.Reactive.Disposables; +using System.Reactive.Linq; +using System.Reactive.Threading.Tasks; +using System.Text; +using GraphQL.Client.Abstractions.Websocket; + +namespace GraphQL.Client.Http.Websocket; + +internal class GraphQLTransportWSProtocolHandler: IWebsocketProtocolHandler +{ + public string WebsocketProtocol => WebSocketProtocols.GRAPHQL_TRANSPORT_WS; + + private readonly GraphQLHttpWebSocket _webSocketHandler; + private readonly GraphQLHttpClient _client; + private readonly Func _queueWebSocketRequest; + private readonly Func _sendWebsocketMessage; + + public GraphQLTransportWSProtocolHandler( + GraphQLHttpWebSocket webSocketHandler, + GraphQLHttpClient client, + Func queueWebSocketRequest, + Func sendWebsocketMessage) + { + _webSocketHandler = webSocketHandler; + _client = client; + _queueWebSocketRequest = queueWebSocketRequest; + _sendWebsocketMessage = sendWebsocketMessage; + } + + public IObservable> CreateSubscriptionObservable(GraphQLRequest request) + => Observable.Create>(async observer => + { + Debug.WriteLine($"Create observable thread id: {Thread.CurrentThread.ManagedThreadId}"); + var preprocessedRequest = await _client.Options.PreprocessRequest(request, _client).ConfigureAwait(false); + + + var startRequest = new GraphQLWebSocketRequest + { + Id = Guid.NewGuid().ToString("N"), + Type = GraphQLWebSocketMessageType.GQL_SUBSCRIBE, + Payload = preprocessedRequest + }; + + + var stopRequest = new GraphQLWebSocketRequest + { + Id = startRequest.Id, + Type = GraphQLWebSocketMessageType.GQL_COMPLETE + }; + + var observable = Observable.Create>(o => + _webSocketHandler.IncomingMessageStream + // ignore null values and messages for other requests + .Where(response => response != null && response.Id == startRequest.Id) + .Subscribe(response => + { + // terminate the sequence when a 'complete' message is received + if (response.Type == GraphQLWebSocketMessageType.GQL_COMPLETE) + { + Debug.WriteLine($"received 'complete' message on subscription {startRequest.Id}"); + o.OnCompleted(); + return; + } + + if (!(response.Type == GraphQLWebSocketMessageType.GQL_NEXT || response.Type == GraphQLWebSocketMessageType.GQL_ERROR)) + throw new WebSocketException("Did not receive 'next' nor 'error'"); + + switch (response.Type) + { + case GraphQLWebSocketMessageType.GQL_NEXT: + // post the GraphQLResponse to the stream (even if a GraphQL error occurred) + Debug.WriteLine($"received payload on subscription {startRequest.Id} (thread {Thread.CurrentThread.ManagedThreadId})"); + var typedResponse = _client.JsonSerializer.DeserializeToWebsocketResponse>(response.MessageBytes); + Debug.WriteLine($"payload => {Encoding.UTF8.GetString(response.MessageBytes)}"); + o.OnNext(typedResponse.Payload); + break; + case GraphQLWebSocketMessageType.GQL_ERROR: + // the payload only consists of the error array + Debug.WriteLine($"received error on subscription {startRequest.Id} (thread {Thread.CurrentThread.ManagedThreadId})"); + Debug.WriteLine($"payload => {Encoding.UTF8.GetString(response.MessageBytes)}"); + var errorResponse = _client.JsonSerializer.DeserializeToWebsocketResponse(response.MessageBytes); + o.OnNext(new GraphQLResponse { Errors = errorResponse.Payload }); + // in case of a GraphQL error, terminate the sequence after the response has been posted + Debug.WriteLine($"terminating subscription {startRequest.Id} because of a GraphQL error"); + o.OnCompleted(); + break; + default: + // If the message type was not 'complete', then it must only be 'next' or 'error' + throw new WebSocketException("Did not receive 'next' nor 'error'"); + } + }, + e => + { + Debug.WriteLine($"response stream for subscription {startRequest.Id} failed: {e}"); + o.OnError(e); + }, + () => + { + Debug.WriteLine($"response stream for subscription {startRequest.Id} completed"); + o.OnCompleted(); + }) + ); + + try + { + // initialize websocket (completes immediately if socket is already open) + await _webSocketHandler.InitializeWebSocket().ConfigureAwait(false); + } + catch (Exception e) + { + // subscribe observer to failed observable + return (CompositeDisposable)Observable.Throw>(e).Subscribe(observer); + } + + var disposable = new CompositeDisposable( + observable.Subscribe(observer), + Disposable.Create(async () => + { + Debug.WriteLine($"disposing subscription {startRequest.Id}, websocket state is '{_webSocketHandler.WebSocketState}'"); + // only try to send close request on open websocket + if (_webSocketHandler.WebSocketState != WebSocketState.Open) + return; + + try + { + Debug.WriteLine($"sending stop message on subscription {startRequest.Id}"); + await _queueWebSocketRequest(stopRequest).ConfigureAwait(false); + } + // do not break on disposing + catch (OperationCanceledException) { } + }) + ); + + Debug.WriteLine($"sending start message on subscription {startRequest.Id}"); + // send subscription request + try + { + await _queueWebSocketRequest(startRequest).ConfigureAwait(false); + } + catch (Exception e) + { + Debug.WriteLine(e); + throw; + } + + return disposable; + }); + + public IObservable> CreateGraphQLRequestObservable(GraphQLRequest request) + => Observable.Create>(async observer => + { + var preprocessedRequest = await _client.Options.PreprocessRequest(request, _client).ConfigureAwait(false); + var websocketRequest = new GraphQLWebSocketRequest + { + Id = Guid.NewGuid().ToString("N"), + Type = GraphQLWebSocketMessageType.GQL_SUBSCRIBE, + Payload = preprocessedRequest + }; + var observable = _webSocketHandler.IncomingMessageStream + .Where(response => response != null && response.Id == websocketRequest.Id) + .TakeUntil(response => response.Type == GraphQLWebSocketMessageType.GQL_COMPLETE) + .Select(response => + { + Debug.WriteLine($"received response for request {websocketRequest.Id}"); + switch (response.Type) + { + case GraphQLWebSocketMessageType.GQL_NEXT: + var typedResponse = _client.JsonSerializer.DeserializeToWebsocketResponse>(response.MessageBytes); + return typedResponse.Payload; + case GraphQLWebSocketMessageType.GQL_ERROR: + // the payload only consists of the error array + var errorResponse = _client.JsonSerializer.DeserializeToWebsocketResponse(response.MessageBytes); + return new GraphQLResponse { Errors = errorResponse.Payload }; + } + return null; + }); + + try + { + // initialize websocket (completes immediately if socket is already open) + await _webSocketHandler.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 _queueWebSocketRequest(websocketRequest).ConfigureAwait(false); + } + catch (Exception e) + { + Debug.WriteLine(e); + throw; + } + + return disposable; + }); + + public IObservable CreatePongObservable() + => _webSocketHandler.IncomingMessageStream + .Where(msg => msg != null && msg.Type == GraphQLWebSocketMessageType.GQL_PONG) + .Select(msg => + { + object? payload = null; + try + { + // try to deserialize response to put it back in the pong request + var responseObject = + _client.JsonSerializer.DeserializeToWebsocketResponse(msg.MessageBytes); + payload = responseObject.Payload; + } + catch (Exception) + { + // ignore exception + } + + return payload; + }); + + public async Task InitializeConnectionAsync(IObservable incomingMessages, + CompositeDisposable closeConnectionDisposable) + { + var initRequest = new GraphQLWebSocketRequest + { + Type = GraphQLWebSocketMessageType.GQL_CONNECTION_INIT, + Payload = _client.Options.ConfigureWebSocketConnectionInitPayload(_client.Options) + }; + + // setup task to await connection_ack message + var ackTask = incomingMessages + .Where(response => response != null) + .TakeUntil(response => response.Type == GraphQLWebSocketMessageType.GQL_CONNECTION_ACK || + response.Type == GraphQLWebSocketMessageType.GQL_CONNECTION_ERROR) + .LastAsync() + .ToTask(); + + // send connection init + Debug.WriteLine($"sending connection init message"); + await _sendWebsocketMessage(initRequest, CancellationToken.None).ConfigureAwait(false); + var response = await ackTask.ConfigureAwait(false); + + if (response.Type != GraphQLWebSocketMessageType.GQL_CONNECTION_ACK) + { + string? errorPayload = Encoding.UTF8.GetString(response.MessageBytes); + Debug.WriteLine($"connection error received: {errorPayload}"); + throw new GraphQLWebsocketConnectionException(errorPayload); + } + + Debug.WriteLine($"connection acknowledged: {Encoding.UTF8.GetString(response.MessageBytes)}"); + + closeConnectionDisposable.Add(incomingMessages + .Where(msg => msg != null && msg.Type == GraphQLWebSocketMessageType.GQL_PING) + .SelectMany(msg => Observable.FromAsync(() => RespondWithPongAsync(msg))) + .Subscribe()); + } + + public Task SendCloseConnectionRequestAsync() + => _sendWebsocketMessage(new GraphQLWebSocketRequest { Type = GraphQLWebSocketMessageType.GQL_COMPLETE }, CancellationToken.None); + + private async Task RespondWithPongAsync(WebsocketMessageWrapper pingMessage) + { + // respond with a PONG when a ping is received + if (pingMessage.Type == GraphQLWebSocketMessageType.GQL_PING) + { + object? payload = null; + try + { + // try to deserialize response to put it back in the pong request + var responseObject = _client.JsonSerializer.DeserializeToWebsocketResponse(pingMessage.MessageBytes); + payload = responseObject.Payload; + } + catch (Exception) + { + // ignore exception + } + Debug.WriteLine($"ping received, responding with pong"); + var pongRequest = new GraphQLWebSocketRequest + { + Type = GraphQLWebSocketMessageType.GQL_PONG, + Payload = payload + }; + + await _queueWebSocketRequest(pongRequest).ConfigureAwait(false); + } + } + + public Task SendPingAsync(object? payload) + { + Debug.WriteLine("sending ping"); + var webSocketRequest = new GraphQLWebSocketRequest + { + Type = GraphQLWebSocketMessageType.GQL_PING, + Payload = payload + }; + + return _queueWebSocketRequest(webSocketRequest); + } + + public Task SendPongAsync(object? payload) + { + Debug.WriteLine("sending pong"); + var webSocketRequest = new GraphQLWebSocketRequest + { + Type = GraphQLWebSocketMessageType.GQL_PONG, + Payload = payload + }; + + return _queueWebSocketRequest(webSocketRequest); + } +} diff --git a/src/GraphQL.Client/Websocket/GraphQLWSProtocolHandler.cs b/src/GraphQL.Client/Websocket/GraphQLWSProtocolHandler.cs new file mode 100644 index 00000000..03efdba4 --- /dev/null +++ b/src/GraphQL.Client/Websocket/GraphQLWSProtocolHandler.cs @@ -0,0 +1,235 @@ +using System.Diagnostics; +using System.Net.WebSockets; +using System.Reactive.Disposables; +using System.Reactive.Linq; +using System.Reactive.Threading.Tasks; +using System.Text; +using GraphQL.Client.Abstractions.Websocket; + +namespace GraphQL.Client.Http.Websocket; + +internal class GraphQLWSProtocolHandler : IWebsocketProtocolHandler +{ + public string WebsocketProtocol => WebSocketProtocols.GRAPHQL_WS; + + private readonly GraphQLHttpWebSocket _webSocketHandler; + private readonly GraphQLHttpClient _client; + private readonly Func _queueWebSocketRequest; + private readonly Func _sendWebsocketMessage; + + public GraphQLWSProtocolHandler( + GraphQLHttpWebSocket webSocketHandler, + GraphQLHttpClient client, + Func queueWebSocketRequest, + Func sendWebsocketMessage) + { + _webSocketHandler = webSocketHandler; + _client = client; + _queueWebSocketRequest = queueWebSocketRequest; + _sendWebsocketMessage = sendWebsocketMessage; + } + + public IObservable> CreateSubscriptionObservable(GraphQLRequest request) + => Observable.Create>(async observer => + { + Debug.WriteLine($"Create observable thread id: {Thread.CurrentThread.ManagedThreadId}"); + var preprocessedRequest = await _client.Options.PreprocessRequest(request, _client).ConfigureAwait(false); + + var startRequest = new GraphQLWebSocketRequest + { + Id = Guid.NewGuid().ToString("N"), + Type = GraphQLWebSocketMessageType.GQL_START, + Payload = preprocessedRequest + }; + var stopRequest = new GraphQLWebSocketRequest + { + Id = startRequest.Id, + Type = GraphQLWebSocketMessageType.GQL_STOP + }; + + var observable = Observable.Create>(o => + _webSocketHandler.IncomingMessageStream + // ignore null values and messages for other requests + .Where(response => response != null && response.Id == startRequest.Id) + .Subscribe(response => + { + // terminate the sequence when a 'complete' message is received + if (response.Type == GraphQLWebSocketMessageType.GQL_COMPLETE) + { + Debug.WriteLine($"received 'complete' message on subscription {startRequest.Id}"); + o.OnCompleted(); + return; + } + + // post the GraphQLResponse to the stream (even if a GraphQL error occurred) + Debug.WriteLine($"received payload on subscription {startRequest.Id} (thread {Thread.CurrentThread.ManagedThreadId})"); + var typedResponse = + _client.JsonSerializer.DeserializeToWebsocketResponse>( + response.MessageBytes); + Debug.WriteLine($"payload => {System.Text.Encoding.UTF8.GetString(response.MessageBytes)}"); + o.OnNext(typedResponse.Payload); + + // in case of a GraphQL error, terminate the sequence after the response has been posted + if (response.Type == GraphQLWebSocketMessageType.GQL_ERROR) + { + Debug.WriteLine($"terminating subscription {startRequest.Id} because of a GraphQL error"); + o.OnCompleted(); + } + }, + e => + { + Debug.WriteLine($"response stream for subscription {startRequest.Id} failed: {e}"); + o.OnError(e); + }, + () => + { + Debug.WriteLine($"response stream for subscription {startRequest.Id} completed"); + o.OnCompleted(); + }) + ); + + try + { + // initialize websocket (completes immediately if socket is already open) + await _webSocketHandler.InitializeWebSocket().ConfigureAwait(false); + } + catch (Exception e) + { + // subscribe observer to failed observable + return Observable.Throw>(e).Subscribe(observer); + } + + var disposable = new CompositeDisposable( + observable.Subscribe(observer), + Disposable.Create(async () => + { + Debug.WriteLine($"disposing subscription {startRequest.Id}, websocket state is '{_webSocketHandler.WebSocketState}'"); + // only try to send close request on open websocket + if (_webSocketHandler.WebSocketState != WebSocketState.Open) + return; + + try + { + Debug.WriteLine($"sending stop message on subscription {startRequest.Id}"); + await _queueWebSocketRequest(stopRequest).ConfigureAwait(false); + } + // do not break on disposing + catch (OperationCanceledException) { } + }) + ); + + Debug.WriteLine($"sending start message on subscription {startRequest.Id}"); + // send subscription request + try + { + await _queueWebSocketRequest(startRequest).ConfigureAwait(false); + } + catch (Exception e) + { + Debug.WriteLine(e); + throw; + } + + return disposable; + }); + + public IObservable> CreateGraphQLRequestObservable(GraphQLRequest request) + => Observable.Create>(async observer => + { + var preprocessedRequest = await _client.Options.PreprocessRequest(request, _client).ConfigureAwait(false); + var websocketRequest = new GraphQLWebSocketRequest + { + Id = Guid.NewGuid().ToString("N"), + Type = GraphQLWebSocketMessageType.GQL_START, + Payload = preprocessedRequest + }; + var observable = _webSocketHandler.IncomingMessageStream + .Where(response => response != null && response.Id == websocketRequest.Id) + .TakeUntil(response => response.Type == GraphQLWebSocketMessageType.GQL_COMPLETE) + .Select(response => + { + Debug.WriteLine($"received response for request {websocketRequest.Id}"); + var typedResponse = + _client.JsonSerializer.DeserializeToWebsocketResponse>( + response.MessageBytes); + return typedResponse.Payload; + }); + + try + { + // initialize websocket (completes immediately if socket is already open) + await _webSocketHandler.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 _queueWebSocketRequest(websocketRequest).ConfigureAwait(false); + } + catch (Exception e) + { + Debug.WriteLine(e); + throw; + } + + return disposable; + }); + + + public async Task InitializeConnectionAsync(IObservable incomingMessages, + CompositeDisposable closeConnectionDisposable) + { + var initRequest = new GraphQLWebSocketRequest + { + Type = GraphQLWebSocketMessageType.GQL_CONNECTION_INIT, + Payload = _client.Options.ConfigureWebSocketConnectionInitPayload(_client.Options) + }; + + // setup task to await connection_ack message + var ackTask = incomingMessages + .Where(response => response != null) + .TakeUntil(response => response.Type == GraphQLWebSocketMessageType.GQL_CONNECTION_ACK || + response.Type == GraphQLWebSocketMessageType.GQL_CONNECTION_ERROR) + .LastAsync() + .ToTask(); + + // send connection init + Debug.WriteLine($"sending connection init message"); + await _sendWebsocketMessage(initRequest, CancellationToken.None).ConfigureAwait(false); + var response = await ackTask.ConfigureAwait(false); + + if (response.Type != GraphQLWebSocketMessageType.GQL_CONNECTION_ACK) + { + string? errorPayload = Encoding.UTF8.GetString(response.MessageBytes); + Debug.WriteLine($"connection error received: {errorPayload}"); + throw new GraphQLWebsocketConnectionException(errorPayload); + } + + Debug.WriteLine($"connection acknowledged: {Encoding.UTF8.GetString(response.MessageBytes)}"); + } + + public Task SendCloseConnectionRequestAsync() + => _sendWebsocketMessage(new GraphQLWebSocketRequest { Type = GraphQLWebSocketMessageType.GQL_CONNECTION_TERMINATE }, CancellationToken.None); + + public IObservable CreatePongObservable() + => throw PingPongNotSupportedException; + + public Task SendPingAsync(object? payload) + => throw PingPongNotSupportedException; + + public Task SendPongAsync(object? payload) + => throw PingPongNotSupportedException; + + private NotSupportedException PingPongNotSupportedException + => new("ping/pong is not supported by the \"graphql-ws\" websocket protocol"); +} diff --git a/src/GraphQL.Client/Websocket/IWebsocketProtocolHandler.cs b/src/GraphQL.Client/Websocket/IWebsocketProtocolHandler.cs new file mode 100644 index 00000000..dc097ee4 --- /dev/null +++ b/src/GraphQL.Client/Websocket/IWebsocketProtocolHandler.cs @@ -0,0 +1,23 @@ +using System.Reactive.Disposables; +using GraphQL.Client.Abstractions.Websocket; + +namespace GraphQL.Client.Http.Websocket; + +public interface IWebsocketProtocolHandler +{ + string WebsocketProtocol { get; } + + IObservable> CreateSubscriptionObservable(GraphQLRequest request); + + IObservable> CreateGraphQLRequestObservable(GraphQLRequest request); + + IObservable CreatePongObservable(); + + Task InitializeConnectionAsync(IObservable incomingMessages, CompositeDisposable closeConnectionDisposable); + + Task SendCloseConnectionRequestAsync(); + + Task SendPingAsync(object? payload); + + Task SendPongAsync(object? payload); +} diff --git a/src/GraphQL.Client/Websocket/WebSocketProtocols.cs b/src/GraphQL.Client/Websocket/WebSocketProtocols.cs new file mode 100644 index 00000000..957f8708 --- /dev/null +++ b/src/GraphQL.Client/Websocket/WebSocketProtocols.cs @@ -0,0 +1,20 @@ +using System.Reflection; + +namespace GraphQL.Client.Http.Websocket; +public static class WebSocketProtocols +{ + public const string AUTO_NEGOTIATE = null; + + //The WebSocket sub-protocol used for the [GraphQL over WebSocket Protocol](https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md). + public const string GRAPHQL_TRANSPORT_WS = "graphql-transport-ws"; + + //The deprecated subprotocol used by [subscriptions-transport-ws](https://github.com/apollographql/subscriptions-transport-ws). + public const string GRAPHQL_WS = "graphql-ws"; + + public static IEnumerable GetSupportedWebSocketProtocols() => + typeof(WebSocketProtocols) + .GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy) + .Where(info => (info.IsLiteral || info.IsInitOnly) && info.FieldType == typeof(string)) + .Select(f => f.IsLiteral ? (string)f.GetRawConstantValue() : (string)f.GetValue(null)) + .Where(s => s is not null); +} diff --git a/tests/GraphQL.Integration.Tests/Helpers/IntegrationServerTestFixture.cs b/tests/GraphQL.Integration.Tests/Helpers/IntegrationServerTestFixture.cs index b0154c2a..5f34b2ba 100644 --- a/tests/GraphQL.Integration.Tests/Helpers/IntegrationServerTestFixture.cs +++ b/tests/GraphQL.Integration.Tests/Helpers/IntegrationServerTestFixture.cs @@ -1,5 +1,6 @@ using GraphQL.Client.Abstractions.Websocket; using GraphQL.Client.Http; +using GraphQL.Client.Http.Websocket; using GraphQL.Client.Serializer.Newtonsoft; using GraphQL.Client.Serializer.SystemTextJson; using GraphQL.Client.Tests.Common; @@ -15,6 +16,8 @@ public abstract class IntegrationServerTestFixture public abstract IGraphQLWebsocketJsonSerializer Serializer { get; } + public abstract string? WebsocketProtocol { get; } + public IntegrationServerTestFixture() { Port = NetworkHelpers.GetFreeTcpPortNumber(); @@ -47,16 +50,35 @@ private GraphQLHttpClient GetGraphQLClient(string endpoint, bool requestsViaWebs { if (Serializer == null) throw new InvalidOperationException("JSON serializer not configured"); - return WebHostHelpers.GetGraphQLClient(Port, endpoint, requestsViaWebsocket, Serializer); + return WebHostHelpers.GetGraphQLClient(Port, endpoint, requestsViaWebsocket, Serializer, WebsocketProtocol); } } -public class NewtonsoftIntegrationServerTestFixture : IntegrationServerTestFixture +public class NewtonsoftGraphQLWsServerTestFixture : IntegrationServerTestFixture +{ + public override IGraphQLWebsocketJsonSerializer Serializer { get; } = new NewtonsoftJsonSerializer(); + public override string? WebsocketProtocol => WebSocketProtocols.GRAPHQL_WS; +} + +public class SystemTextJsonGraphQLWsServerTestFixture : IntegrationServerTestFixture +{ + public override IGraphQLWebsocketJsonSerializer Serializer { get; } = new SystemTextJsonSerializer(); + public override string? WebsocketProtocol => WebSocketProtocols.GRAPHQL_WS; +} + +public class NewtonsoftGraphQLTransportWsServerTestFixture : IntegrationServerTestFixture { public override IGraphQLWebsocketJsonSerializer Serializer { get; } = new NewtonsoftJsonSerializer(); + public override string? WebsocketProtocol => WebSocketProtocols.GRAPHQL_TRANSPORT_WS; } -public class SystemTextJsonIntegrationServerTestFixture : IntegrationServerTestFixture +public class SystemTextJsonGraphQLTransportWsServerTestFixture : IntegrationServerTestFixture +{ + public override IGraphQLWebsocketJsonSerializer Serializer { get; } = new SystemTextJsonSerializer(); + public override string? WebsocketProtocol => WebSocketProtocols.GRAPHQL_TRANSPORT_WS; +} +public class SystemTextJsonAutoNegotiateServerTestFixture : IntegrationServerTestFixture { public override IGraphQLWebsocketJsonSerializer Serializer { get; } = new SystemTextJsonSerializer(); + public override string? WebsocketProtocol => WebSocketProtocols.AUTO_NEGOTIATE; } diff --git a/tests/GraphQL.Integration.Tests/Helpers/WebHostHelpers.cs b/tests/GraphQL.Integration.Tests/Helpers/WebHostHelpers.cs index ac90c08b..609a1413 100644 --- a/tests/GraphQL.Integration.Tests/Helpers/WebHostHelpers.cs +++ b/tests/GraphQL.Integration.Tests/Helpers/WebHostHelpers.cs @@ -1,5 +1,6 @@ using GraphQL.Client.Abstractions.Websocket; using GraphQL.Client.Http; +using GraphQL.Client.Http.Websocket; using GraphQL.Client.Serializer.Newtonsoft; using GraphQL.Client.Tests.Common; using GraphQL.Client.Tests.Common.Helpers; @@ -30,11 +31,12 @@ public static async Task CreateServer(int port) return host; } - public static GraphQLHttpClient GetGraphQLClient(int port, string endpoint, bool requestsViaWebsocket = false, IGraphQLWebsocketJsonSerializer serializer = null) + public static GraphQLHttpClient GetGraphQLClient(int port, string endpoint, bool requestsViaWebsocket = false, IGraphQLWebsocketJsonSerializer serializer = null, string websocketProtocol = WebSocketProtocols.GRAPHQL_WS) => new GraphQLHttpClient(new GraphQLHttpClientOptions { EndPoint = new Uri($"http://localhost:{port}{endpoint}"), - UseWebSocketForQueriesAndMutations = requestsViaWebsocket + UseWebSocketForQueriesAndMutations = requestsViaWebsocket, + WebSocketProtocol = websocketProtocol }, serializer ?? new NewtonsoftJsonSerializer()); } diff --git a/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Newtonsoft.cs b/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Newtonsoft.cs index 768970e4..78539df0 100644 --- a/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Newtonsoft.cs +++ b/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Newtonsoft.cs @@ -3,9 +3,9 @@ namespace GraphQL.Integration.Tests.QueryAndMutationTests; -public class Newtonsoft : Base, IClassFixture +public class Newtonsoft : Base, IClassFixture { - public Newtonsoft(NewtonsoftIntegrationServerTestFixture fixture) : base(fixture) + public Newtonsoft(NewtonsoftGraphQLWsServerTestFixture fixture) : base(fixture) { } } diff --git a/tests/GraphQL.Integration.Tests/QueryAndMutationTests/SystemTextJson.cs b/tests/GraphQL.Integration.Tests/QueryAndMutationTests/SystemTextJson.cs index 9985692c..d42abb21 100644 --- a/tests/GraphQL.Integration.Tests/QueryAndMutationTests/SystemTextJson.cs +++ b/tests/GraphQL.Integration.Tests/QueryAndMutationTests/SystemTextJson.cs @@ -3,9 +3,9 @@ namespace GraphQL.Integration.Tests.QueryAndMutationTests; -public class SystemTextJson : Base, IClassFixture +public class SystemTextJson : Base, IClassFixture { - public SystemTextJson(SystemTextJsonIntegrationServerTestFixture fixture) : base(fixture) + public SystemTextJson(SystemTextJsonGraphQLWsServerTestFixture fixture) : base(fixture) { } } diff --git a/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs b/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs index 8978bc35..9c730e4c 100644 --- a/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs +++ b/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs @@ -476,5 +476,4 @@ public async void CanHandleQueryErrorInSubscription() await observer.Should().CompleteAsync(); ChatClient.Dispose(); } - } diff --git a/tests/GraphQL.Integration.Tests/WebsocketTests/Newtonsoft.cs b/tests/GraphQL.Integration.Tests/WebsocketTests/Newtonsoft.cs deleted file mode 100644 index 87c23ba6..00000000 --- a/tests/GraphQL.Integration.Tests/WebsocketTests/Newtonsoft.cs +++ /dev/null @@ -1,12 +0,0 @@ -using GraphQL.Integration.Tests.Helpers; -using Xunit; -using Xunit.Abstractions; - -namespace GraphQL.Integration.Tests.WebsocketTests; - -public class Newtonsoft : Base, IClassFixture -{ - public Newtonsoft(ITestOutputHelper output, NewtonsoftIntegrationServerTestFixture fixture) : base(output, fixture) - { - } -} diff --git a/tests/GraphQL.Integration.Tests/WebsocketTests/NewtonsoftGraphQLTransportWs.cs b/tests/GraphQL.Integration.Tests/WebsocketTests/NewtonsoftGraphQLTransportWs.cs new file mode 100644 index 00000000..0a56d631 --- /dev/null +++ b/tests/GraphQL.Integration.Tests/WebsocketTests/NewtonsoftGraphQLTransportWs.cs @@ -0,0 +1,12 @@ +using GraphQL.Integration.Tests.Helpers; +using Xunit; +using Xunit.Abstractions; + +namespace GraphQL.Integration.Tests.WebsocketTests; + +public class NewtonsoftGraphQLTransportWs : Base, IClassFixture +{ + public NewtonsoftGraphQLTransportWs(ITestOutputHelper output, NewtonsoftGraphQLTransportWsServerTestFixture fixture) : base(output, fixture) + { + } +} diff --git a/tests/GraphQL.Integration.Tests/WebsocketTests/NewtonsoftGraphQLWs.cs b/tests/GraphQL.Integration.Tests/WebsocketTests/NewtonsoftGraphQLWs.cs new file mode 100644 index 00000000..c20b133c --- /dev/null +++ b/tests/GraphQL.Integration.Tests/WebsocketTests/NewtonsoftGraphQLWs.cs @@ -0,0 +1,12 @@ +using GraphQL.Integration.Tests.Helpers; +using Xunit; +using Xunit.Abstractions; + +namespace GraphQL.Integration.Tests.WebsocketTests; + +public class NewtonsoftGraphQLWs : Base, IClassFixture +{ + public NewtonsoftGraphQLWs(ITestOutputHelper output, NewtonsoftGraphQLWsServerTestFixture fixture) : base(output, fixture) + { + } +} diff --git a/tests/GraphQL.Integration.Tests/WebsocketTests/SystemTextJson.cs b/tests/GraphQL.Integration.Tests/WebsocketTests/SystemTextJson.cs deleted file mode 100644 index e3648bf4..00000000 --- a/tests/GraphQL.Integration.Tests/WebsocketTests/SystemTextJson.cs +++ /dev/null @@ -1,12 +0,0 @@ -using GraphQL.Integration.Tests.Helpers; -using Xunit; -using Xunit.Abstractions; - -namespace GraphQL.Integration.Tests.WebsocketTests; - -public class SystemTextJson : Base, IClassFixture -{ - public SystemTextJson(ITestOutputHelper output, SystemTextJsonIntegrationServerTestFixture fixture) : base(output, fixture) - { - } -} diff --git a/tests/GraphQL.Integration.Tests/WebsocketTests/SystemTextJsonAutoNegotiate.cs b/tests/GraphQL.Integration.Tests/WebsocketTests/SystemTextJsonAutoNegotiate.cs new file mode 100644 index 00000000..0090e307 --- /dev/null +++ b/tests/GraphQL.Integration.Tests/WebsocketTests/SystemTextJsonAutoNegotiate.cs @@ -0,0 +1,21 @@ +using FluentAssertions; +using GraphQL.Client.Http.Websocket; +using GraphQL.Integration.Tests.Helpers; +using Xunit; +using Xunit.Abstractions; + +namespace GraphQL.Integration.Tests.WebsocketTests; + +public class SystemTextJsonAutoNegotiate : Base, IClassFixture +{ + public SystemTextJsonAutoNegotiate(ITestOutputHelper output, SystemTextJsonAutoNegotiateServerTestFixture fixture) : base(output, fixture) + { + } + + [Fact] + public async Task WebSocketProtocolIsAutoNegotiated() + { + await ChatClient.InitializeWebsocketConnection().ConfigureAwait(false); + ChatClient.WebSocketSubProtocol.Should().Be(WebSocketProtocols.GRAPHQL_TRANSPORT_WS); + } +} diff --git a/tests/GraphQL.Integration.Tests/WebsocketTests/SystemTextJsonGraphQLTransportWs.cs b/tests/GraphQL.Integration.Tests/WebsocketTests/SystemTextJsonGraphQLTransportWs.cs new file mode 100644 index 00000000..dfba089e --- /dev/null +++ b/tests/GraphQL.Integration.Tests/WebsocketTests/SystemTextJsonGraphQLTransportWs.cs @@ -0,0 +1,35 @@ +using FluentAssertions; +using FluentAssertions.Reactive; +using GraphQL.Integration.Tests.Helpers; +using Xunit; +using Xunit.Abstractions; + +namespace GraphQL.Integration.Tests.WebsocketTests; + +public class SystemTextJsonGraphQLTransportWs : Base, IClassFixture +{ + public SystemTextJsonGraphQLTransportWs(ITestOutputHelper output, SystemTextJsonGraphQLTransportWsServerTestFixture fixture) : base(output, fixture) + { + } + + [Fact] + public async void Sending_a_pong_message_should_not_throw() + { + await ChatClient.InitializeWebsocketConnection().ConfigureAwait(false); + + await ChatClient.Awaiting(client => client.SendPongAsync(null)).Should().NotThrowAsync().ConfigureAwait(false); + await ChatClient.Awaiting(client => client.SendPongAsync("some payload")).Should().NotThrowAsync().ConfigureAwait(false); + } + + [Fact] + public async void Sending_a_ping_message_should_result_in_a_pong_message_from_the_server() + { + await ChatClient.InitializeWebsocketConnection().ConfigureAwait(false); + + using var pongObserver = ChatClient.PongStream.Observe(); + + await ChatClient.Awaiting(client => client.SendPingAsync(null)).Should().NotThrowAsync().ConfigureAwait(false); + + await pongObserver.Should().PushAsync(1, TimeSpan.FromSeconds(1), "because the server was pinged by the client").ConfigureAwait(false); + } +} diff --git a/tests/GraphQL.Integration.Tests/WebsocketTests/SystemTextJsonGraphQLWs.cs b/tests/GraphQL.Integration.Tests/WebsocketTests/SystemTextJsonGraphQLWs.cs new file mode 100644 index 00000000..963f8292 --- /dev/null +++ b/tests/GraphQL.Integration.Tests/WebsocketTests/SystemTextJsonGraphQLWs.cs @@ -0,0 +1,40 @@ +using FluentAssertions; +using GraphQL.Integration.Tests.Helpers; +using Xunit; +using Xunit.Abstractions; + +namespace GraphQL.Integration.Tests.WebsocketTests; + +public class SystemTextJsonGraphQLWs : Base, IClassFixture +{ + public SystemTextJsonGraphQLWs(ITestOutputHelper output, SystemTextJsonGraphQLWsServerTestFixture fixture) : base(output, fixture) + { + } + + [Fact] + public async void Sending_a_ping_message_should_throw_not_supported_exception() + { + await ChatClient.InitializeWebsocketConnection().ConfigureAwait(false); + + await ChatClient.Awaiting(client => client.SendPingAsync(null)) + .Should().ThrowAsync().ConfigureAwait(false); + } + + [Fact] + public async void Sending_a_pong_message_should_throw_not_supported_exception() + { + await ChatClient.InitializeWebsocketConnection().ConfigureAwait(false); + + await ChatClient.Awaiting(client => client.SendPongAsync(null)) + .Should().ThrowAsync().ConfigureAwait(false); + } + + [Fact] + public async void Subscribing_to_the_pong_stream_should_throw_not_supported_exception() + { + await ChatClient.InitializeWebsocketConnection().ConfigureAwait(false); + + ChatClient.Invoking(client => client.PongStream.Subscribe()) + .Should().Throw(); + } +} From 9f7d59307c50bbfdfb081a7b5c11ca0b779e777b Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Thu, 13 Apr 2023 18:16:24 +0200 Subject: [PATCH 428/455] Feature/make automatic header optional (#542) * Make UserAgent request header optional. * Changed to configurable user agent header. * Update src/GraphQL.Client/GraphQLHttpClientOptions.cs Co-authored-by: Alexander Rose * Update src/GraphQL.Client/GraphQLHttpRequest.cs Co-authored-by: Alexander Rose * Update src/GraphQL.Client/GraphQLHttpRequest.cs Co-authored-by: Alexander Rose * Update src/GraphQL.Client/GraphQLHttpRequest.cs Co-authored-by: Alexander Rose * Update src/GraphQL.Client/GraphQLHttpRequest.cs * refactor test helpers to provide access to GraphQLHttpClientOptions when creating the test client * test user agent header --------- Co-authored-by: Jesse Co-authored-by: Ivan Maximov --- src/GraphQL.Client/GraphQLHttpClient.cs | 18 ++--- .../GraphQLHttpClientOptions.cs | 7 ++ src/GraphQL.Client/GraphQLHttpRequest.cs | 3 + .../Chat/Schema/ChatQuery.cs | 19 +++++- .../GraphQL.Client.Tests.Common.csproj | 5 +- .../Properties/launchSettings.json | 12 ++++ .../Helpers/IntegrationServerTestFixture.cs | 26 +++---- .../Helpers/WebHostHelpers.cs | 42 +++--------- .../UserAgentHeaderTests.cs | 68 +++++++++++++++++++ .../WebsocketTests/Base.cs | 4 +- tests/IntegrationTestServer/Startup.cs | 2 +- 11 files changed, 145 insertions(+), 61 deletions(-) create mode 100644 tests/GraphQL.Client.Tests.Common/Properties/launchSettings.json create mode 100644 tests/GraphQL.Integration.Tests/UserAgentHeaderTests.cs diff --git a/src/GraphQL.Client/GraphQLHttpClient.cs b/src/GraphQL.Client/GraphQLHttpClient.cs index d634fe2e..82c27452 100644 --- a/src/GraphQL.Client/GraphQLHttpClient.cs +++ b/src/GraphQL.Client/GraphQLHttpClient.cs @@ -60,17 +60,13 @@ public GraphQLHttpClient(GraphQLHttpClientOptions options, IGraphQLWebsocketJson _disposeHttpClient = true; } - public GraphQLHttpClient(GraphQLHttpClientOptions options, IGraphQLWebsocketJsonSerializer serializer, HttpClient httpClient) - { - Options = options ?? throw new ArgumentNullException(nameof(options)); - JsonSerializer = serializer ?? throw new ArgumentNullException(nameof(serializer), "please configure the JSON serializer you want to use"); - HttpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); - - if (!HttpClient.DefaultRequestHeaders.UserAgent.Any()) - HttpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(GetType().Assembly.GetName().Name, GetType().Assembly.GetName().Version.ToString())); - - _lazyHttpWebSocket = new Lazy(CreateGraphQLHttpWebSocket); - } + public GraphQLHttpClient(GraphQLHttpClientOptions options, IGraphQLWebsocketJsonSerializer serializer, HttpClient httpClient) + { + Options = options ?? throw new ArgumentNullException(nameof(options)); + JsonSerializer = serializer ?? throw new ArgumentNullException(nameof(serializer), "please configure the JSON serializer you want to use"); + HttpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); + _lazyHttpWebSocket = new Lazy(CreateGraphQLHttpWebSocket); + } #endregion diff --git a/src/GraphQL.Client/GraphQLHttpClientOptions.cs b/src/GraphQL.Client/GraphQLHttpClientOptions.cs index 74de6441..13a9c576 100644 --- a/src/GraphQL.Client/GraphQLHttpClientOptions.cs +++ b/src/GraphQL.Client/GraphQLHttpClientOptions.cs @@ -80,4 +80,11 @@ public class GraphQLHttpClientOptions /// See https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md#gql_connection_init. /// public Func ConfigureWebSocketConnectionInitPayload { get; set; } = options => null; + + /// + /// The default user agent request header. + /// Default to the GraphQL client assembly. + /// + public ProductInfoHeaderValue? DefaultUserAgentRequestHeader { get; set; } + = new ProductInfoHeaderValue(typeof(GraphQLHttpClient).Assembly.GetName().Name, typeof(GraphQLHttpClient).Assembly.GetName().Version.ToString()); } diff --git a/src/GraphQL.Client/GraphQLHttpRequest.cs b/src/GraphQL.Client/GraphQLHttpRequest.cs index 925c060e..6d3a7633 100644 --- a/src/GraphQL.Client/GraphQLHttpRequest.cs +++ b/src/GraphQL.Client/GraphQLHttpRequest.cs @@ -43,6 +43,9 @@ public virtual HttpRequestMessage ToHttpRequestMessage(GraphQLHttpClientOptions message.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); message.Headers.AcceptCharset.Add(new StringWithQualityHeaderValue("utf-8")); + if (options.DefaultUserAgentRequestHeader != null) + message.Headers.UserAgent.Add(options.DefaultUserAgentRequestHeader); + #pragma warning disable CS0618 // Type or member is obsolete PreprocessHttpRequestMessage(message); #pragma warning restore CS0618 // Type or member is obsolete diff --git a/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatQuery.cs b/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatQuery.cs index 61bf6055..5b506225 100644 --- a/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatQuery.cs +++ b/tests/GraphQL.Client.Tests.Common/Chat/Schema/ChatQuery.cs @@ -1,9 +1,13 @@ using GraphQL.Types; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; namespace GraphQL.Client.Tests.Common.Chat.Schema; public class ChatQuery : ObjectGraphType { + private readonly IServiceProvider _serviceProvider; + public static readonly Dictionary TestExtensions = new() { {"extension1", "hello world"}, @@ -16,8 +20,9 @@ public class ChatQuery : ObjectGraphType public readonly ManualResetEventSlim LongRunningQueryBlocker = new ManualResetEventSlim(); public readonly ManualResetEventSlim WaitingOnQueryBlocker = new ManualResetEventSlim(); - public ChatQuery(IChat chat) + public ChatQuery(IChat chat, IServiceProvider serviceProvider) { + _serviceProvider = serviceProvider; Name = "ChatQuery"; Field>("messages").Resolve(context => chat.AllMessages.Take(100)); @@ -37,5 +42,17 @@ public ChatQuery(IChat chat) WaitingOnQueryBlocker.Reset(); return "finally returned"; }); + + Field("clientUserAgent") + .Resolve(context => + { + var contextAccessor = _serviceProvider.GetRequiredService(); + if (!contextAccessor.HttpContext.Request.Headers.UserAgent.Any()) + { + context.Errors.Add(new ExecutionError("user agent header not set")); + return null; + } + return contextAccessor.HttpContext.Request.Headers.UserAgent.ToString(); + }); } } diff --git a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj index 9bfadf6b..23bce138 100644 --- a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj +++ b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj @@ -1,7 +1,7 @@ - netstandard2.0 + net7.0 false @@ -16,4 +16,7 @@ + + + diff --git a/tests/GraphQL.Client.Tests.Common/Properties/launchSettings.json b/tests/GraphQL.Client.Tests.Common/Properties/launchSettings.json new file mode 100644 index 00000000..a50815da --- /dev/null +++ b/tests/GraphQL.Client.Tests.Common/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "GraphQL.Client.Tests.Common": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "http://localhost:59034" + } + } +} \ No newline at end of file diff --git a/tests/GraphQL.Integration.Tests/Helpers/IntegrationServerTestFixture.cs b/tests/GraphQL.Integration.Tests/Helpers/IntegrationServerTestFixture.cs index 5f34b2ba..eca64acc 100644 --- a/tests/GraphQL.Integration.Tests/Helpers/IntegrationServerTestFixture.cs +++ b/tests/GraphQL.Integration.Tests/Helpers/IntegrationServerTestFixture.cs @@ -12,7 +12,7 @@ public abstract class IntegrationServerTestFixture { public int Port { get; private set; } - public IWebHost Server { get; private set; } + public IWebHost? Server { get; private set; } public abstract IGraphQLWebsocketJsonSerializer Serializer { get; } @@ -27,7 +27,7 @@ public async Task CreateServer() { if (Server != null) return; - Server = await WebHostHelpers.CreateServer(Port); + Server = await WebHostHelpers.CreateServer(Port).ConfigureAwait(false); } public async Task ShutdownServer() @@ -40,18 +40,20 @@ public async Task ShutdownServer() Server = null; } - public GraphQLHttpClient GetStarWarsClient(bool requestsViaWebsocket = false) - => GetGraphQLClient(Common.STAR_WARS_ENDPOINT, requestsViaWebsocket); + public GraphQLHttpClient GetStarWarsClient(Action? configure = null) + => GetGraphQLClient(Common.STAR_WARS_ENDPOINT, configure); - public GraphQLHttpClient GetChatClient(bool requestsViaWebsocket = false) - => GetGraphQLClient(Common.CHAT_ENDPOINT, requestsViaWebsocket); + public GraphQLHttpClient GetChatClient(Action? configure = null) + => GetGraphQLClient(Common.CHAT_ENDPOINT, configure); - private GraphQLHttpClient GetGraphQLClient(string endpoint, bool requestsViaWebsocket = false) - { - if (Serializer == null) - throw new InvalidOperationException("JSON serializer not configured"); - return WebHostHelpers.GetGraphQLClient(Port, endpoint, requestsViaWebsocket, Serializer, WebsocketProtocol); - } + private GraphQLHttpClient GetGraphQLClient(string endpoint, Action? configure) => + Serializer == null + ? throw new InvalidOperationException("JSON serializer not configured") + : WebHostHelpers.GetGraphQLClient(Port, endpoint, Serializer, options => + { + configure?.Invoke(options); + options.WebSocketProtocol = WebsocketProtocol; + }); } public class NewtonsoftGraphQLWsServerTestFixture : IntegrationServerTestFixture diff --git a/tests/GraphQL.Integration.Tests/Helpers/WebHostHelpers.cs b/tests/GraphQL.Integration.Tests/Helpers/WebHostHelpers.cs index 609a1413..e077fe10 100644 --- a/tests/GraphQL.Integration.Tests/Helpers/WebHostHelpers.cs +++ b/tests/GraphQL.Integration.Tests/Helpers/WebHostHelpers.cs @@ -2,8 +2,6 @@ using GraphQL.Client.Http; using GraphQL.Client.Http.Websocket; using GraphQL.Client.Serializer.Newtonsoft; -using GraphQL.Client.Tests.Common; -using GraphQL.Client.Tests.Common.Helpers; using IntegrationTestServer; namespace GraphQL.Integration.Tests.Helpers; @@ -31,37 +29,15 @@ public static async Task CreateServer(int port) return host; } - public static GraphQLHttpClient GetGraphQLClient(int port, string endpoint, bool requestsViaWebsocket = false, IGraphQLWebsocketJsonSerializer serializer = null, string websocketProtocol = WebSocketProtocols.GRAPHQL_WS) - => new GraphQLHttpClient(new GraphQLHttpClientOptions - { - EndPoint = new Uri($"http://localhost:{port}{endpoint}"), - UseWebSocketForQueriesAndMutations = requestsViaWebsocket, - WebSocketProtocol = websocketProtocol - }, - serializer ?? new NewtonsoftJsonSerializer()); -} - -public class TestServerSetup : IDisposable -{ - public TestServerSetup(IGraphQLWebsocketJsonSerializer serializer) + public static GraphQLHttpClient GetGraphQLClient( + int port, + string endpoint, + IGraphQLWebsocketJsonSerializer? serializer = null, + Action? configure = null) { - Serializer = serializer; - Port = NetworkHelpers.GetFreeTcpPortNumber(); + var options = new GraphQLHttpClientOptions(); + configure?.Invoke(options); + options.EndPoint = new Uri($"http://localhost:{port}{endpoint}"); + return new GraphQLHttpClient(options, serializer ?? new NewtonsoftJsonSerializer()); } - - public int Port { get; } - - public IWebHost Server { get; set; } - - public IGraphQLWebsocketJsonSerializer Serializer { get; set; } - - public GraphQLHttpClient GetStarWarsClient(bool requestsViaWebsocket = false) - => GetGraphQLClient(Common.STAR_WARS_ENDPOINT, requestsViaWebsocket); - - public GraphQLHttpClient GetChatClient(bool requestsViaWebsocket = false) - => GetGraphQLClient(Common.CHAT_ENDPOINT, requestsViaWebsocket); - - private GraphQLHttpClient GetGraphQLClient(string endpoint, bool requestsViaWebsocket = false) => WebHostHelpers.GetGraphQLClient(Port, endpoint, requestsViaWebsocket); - - public void Dispose() => Server?.Dispose(); } diff --git a/tests/GraphQL.Integration.Tests/UserAgentHeaderTests.cs b/tests/GraphQL.Integration.Tests/UserAgentHeaderTests.cs new file mode 100644 index 00000000..bf631be5 --- /dev/null +++ b/tests/GraphQL.Integration.Tests/UserAgentHeaderTests.cs @@ -0,0 +1,68 @@ +using System.Net.Http.Headers; +using FluentAssertions; +using GraphQL.Client.Abstractions; +using GraphQL.Client.Http; +using GraphQL.Integration.Tests.Helpers; +using Xunit; + +namespace GraphQL.Integration.Tests; + +public class UserAgentHeaderTests : IAsyncLifetime, IClassFixture +{ + private readonly IntegrationServerTestFixture Fixture; + private GraphQLHttpClient? ChatClient; + + public UserAgentHeaderTests(SystemTextJsonAutoNegotiateServerTestFixture fixture) + { + Fixture = fixture; + } + + public async Task InitializeAsync() => await Fixture.CreateServer().ConfigureAwait(false); + + public Task DisposeAsync() + { + ChatClient?.Dispose(); + return Task.CompletedTask; + } + + [Fact] + public async void Can_set_custom_user_agent() + { + const string userAgent = "CustomUserAgent"; + ChatClient = Fixture.GetChatClient(options => options.DefaultUserAgentRequestHeader = ProductInfoHeaderValue.Parse(userAgent)); + + var graphQLRequest = new GraphQLRequest("query clientUserAgent { clientUserAgent }"); + var response = await ChatClient.SendQueryAsync(graphQLRequest, () => new { clientUserAgent = string.Empty }).ConfigureAwait(false); + + response.Errors.Should().BeNull(); + response.Data.clientUserAgent.Should().Be(userAgent); + } + + [Fact] + public async void Default_user_agent_is_set_as_expected() + { + string? expectedUserAgent = new ProductInfoHeaderValue( + typeof(GraphQLHttpClient).Assembly.GetName().Name, + typeof(GraphQLHttpClient).Assembly.GetName().Version.ToString()).ToString(); + + ChatClient = Fixture.GetChatClient(); + + var graphQLRequest = new GraphQLRequest("query clientUserAgent { clientUserAgent }"); + var response = await ChatClient.SendQueryAsync(graphQLRequest, () => new { clientUserAgent = string.Empty }).ConfigureAwait(false); + + response.Errors.Should().BeNull(); + response.Data.clientUserAgent.Should().Be(expectedUserAgent); + } + + [Fact] + public async void No_Default_user_agent_if_set_to_null() + { + ChatClient = Fixture.GetChatClient(options => options.DefaultUserAgentRequestHeader = null); + + var graphQLRequest = new GraphQLRequest("query clientUserAgent { clientUserAgent }"); + var response = await ChatClient.SendQueryAsync(graphQLRequest, () => new { clientUserAgent = string.Empty }).ConfigureAwait(false); + + response.Errors.Should().HaveCount(1); + response.Errors[0].Message.Should().Be("user agent header not set"); + } +} diff --git a/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs b/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs index 9c730e4c..e16a4a38 100644 --- a/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs +++ b/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs @@ -21,7 +21,7 @@ public abstract class Base : IAsyncLifetime { protected readonly ITestOutputHelper Output; protected readonly IntegrationServerTestFixture Fixture; - protected GraphQLHttpClient ChatClient; + protected GraphQLHttpClient? ChatClient; protected Base(ITestOutputHelper output, IntegrationServerTestFixture fixture) { @@ -43,7 +43,7 @@ public async Task InitializeAsync() Fixture.Server.Services.GetService().AddMessage(InitialMessage); // then create the chat client - ChatClient ??= Fixture.GetChatClient(true); + ChatClient ??= Fixture.GetChatClient(options => options.UseWebSocketForQueriesAndMutations = true); } public Task DisposeAsync() diff --git a/tests/IntegrationTestServer/Startup.cs b/tests/IntegrationTestServer/Startup.cs index d2b8d427..ee8526c1 100644 --- a/tests/IntegrationTestServer/Startup.cs +++ b/tests/IntegrationTestServer/Startup.cs @@ -25,7 +25,7 @@ public Startup(IConfiguration configuration, IWebHostEnvironment environment) public void ConfigureServices(IServiceCollection services) { services.Configure(options => options.AllowSynchronousIO = true); - // + services.AddHttpContextAccessor(); services.AddChatSchema(); services.AddStarWarsSchema(); services.AddGraphQL(builder => builder From 37c547eabcbd5c6a3e07852a4bd9ed475a6927d2 Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Sun, 16 Apr 2023 16:00:40 +0300 Subject: [PATCH 429/455] Bump GraphQL.NET to v7.4 (#547) --- .../GraphQLWebSocketMessageType.cs | 1 - .../GraphQL.Client.LocalExecution.csproj | 2 +- .../GraphQL.Client.Serializer.Newtonsoft.csproj | 4 ++-- .../ConstantCaseJsonNamingPolicy.cs | 2 +- src/GraphQL.Client/GraphQLHttpClient.cs | 15 +++++++-------- .../GraphQLTransportWSProtocolHandler.cs | 2 +- .../GraphQL.Client.Serializer.Tests.csproj | 6 +++--- .../GraphQL.Client.Tests.Common.csproj | 4 ++-- .../StarWars/Types/CharacterInterface.cs | 4 ++-- .../Helpers/WebHostHelpers.cs | 1 - .../GraphQL.Server.Test.csproj | 6 +++--- .../IntegrationTestServer.csproj | 4 ++-- 12 files changed, 24 insertions(+), 27 deletions(-) diff --git a/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketMessageType.cs b/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketMessageType.cs index a1628c6e..fbc3a5f5 100644 --- a/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketMessageType.cs +++ b/src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketMessageType.cs @@ -134,5 +134,4 @@ public static class GraphQLWebSocketMessageType /// public const string GQL_NEXT = "next"; // Server -> Client - } diff --git a/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj b/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj index 253f9283..7971ef82 100644 --- a/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj +++ b/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj @@ -6,7 +6,7 @@ - + diff --git a/src/GraphQL.Client.Serializer.Newtonsoft/GraphQL.Client.Serializer.Newtonsoft.csproj b/src/GraphQL.Client.Serializer.Newtonsoft/GraphQL.Client.Serializer.Newtonsoft.csproj index a3c9700a..22126805 100644 --- a/src/GraphQL.Client.Serializer.Newtonsoft/GraphQL.Client.Serializer.Newtonsoft.csproj +++ b/src/GraphQL.Client.Serializer.Newtonsoft/GraphQL.Client.Serializer.Newtonsoft.csproj @@ -1,4 +1,4 @@ - + A serializer implementation for GraphQL.Client using Newtonsoft.Json as underlying JSON library @@ -6,7 +6,7 @@ - + diff --git a/src/GraphQL.Client.Serializer.SystemTextJson/ConstantCaseJsonNamingPolicy.cs b/src/GraphQL.Client.Serializer.SystemTextJson/ConstantCaseJsonNamingPolicy.cs index acc7f9b1..f8560306 100644 --- a/src/GraphQL.Client.Serializer.SystemTextJson/ConstantCaseJsonNamingPolicy.cs +++ b/src/GraphQL.Client.Serializer.SystemTextJson/ConstantCaseJsonNamingPolicy.cs @@ -1,4 +1,4 @@ -using System.Text.Json; +using System.Text.Json; using GraphQL.Client.Abstractions.Utilities; namespace GraphQL.Client.Serializer.SystemTextJson; diff --git a/src/GraphQL.Client/GraphQLHttpClient.cs b/src/GraphQL.Client/GraphQLHttpClient.cs index 82c27452..7ffea50b 100644 --- a/src/GraphQL.Client/GraphQLHttpClient.cs +++ b/src/GraphQL.Client/GraphQLHttpClient.cs @@ -1,5 +1,4 @@ using System.Diagnostics; -using System.Net.Http.Headers; using GraphQL.Client.Abstractions; using GraphQL.Client.Abstractions.Websocket; using GraphQL.Client.Http.Websocket; @@ -60,13 +59,13 @@ public GraphQLHttpClient(GraphQLHttpClientOptions options, IGraphQLWebsocketJson _disposeHttpClient = true; } - public GraphQLHttpClient(GraphQLHttpClientOptions options, IGraphQLWebsocketJsonSerializer serializer, HttpClient httpClient) - { - Options = options ?? throw new ArgumentNullException(nameof(options)); - JsonSerializer = serializer ?? throw new ArgumentNullException(nameof(serializer), "please configure the JSON serializer you want to use"); - HttpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); - _lazyHttpWebSocket = new Lazy(CreateGraphQLHttpWebSocket); - } + public GraphQLHttpClient(GraphQLHttpClientOptions options, IGraphQLWebsocketJsonSerializer serializer, HttpClient httpClient) + { + Options = options ?? throw new ArgumentNullException(nameof(options)); + JsonSerializer = serializer ?? throw new ArgumentNullException(nameof(serializer), "please configure the JSON serializer you want to use"); + HttpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); + _lazyHttpWebSocket = new Lazy(CreateGraphQLHttpWebSocket); + } #endregion diff --git a/src/GraphQL.Client/Websocket/GraphQLTransportWSProtocolHandler.cs b/src/GraphQL.Client/Websocket/GraphQLTransportWSProtocolHandler.cs index c78581b1..1ac5ffeb 100644 --- a/src/GraphQL.Client/Websocket/GraphQLTransportWSProtocolHandler.cs +++ b/src/GraphQL.Client/Websocket/GraphQLTransportWSProtocolHandler.cs @@ -8,7 +8,7 @@ namespace GraphQL.Client.Http.Websocket; -internal class GraphQLTransportWSProtocolHandler: IWebsocketProtocolHandler +internal class GraphQLTransportWSProtocolHandler : IWebsocketProtocolHandler { public string WebsocketProtocol => WebSocketProtocols.GRAPHQL_TRANSPORT_WS; diff --git a/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj b/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj index fac5bf23..2daa9c99 100644 --- a/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj +++ b/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj @@ -1,4 +1,4 @@ - + @@ -7,8 +7,8 @@ - - + + diff --git a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj index 23bce138..98393c8f 100644 --- a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj +++ b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj @@ -1,4 +1,4 @@ - + net7.0 @@ -7,7 +7,7 @@ - + diff --git a/tests/GraphQL.Client.Tests.Common/StarWars/Types/CharacterInterface.cs b/tests/GraphQL.Client.Tests.Common/StarWars/Types/CharacterInterface.cs index a25daec3..0cf9d6b2 100644 --- a/tests/GraphQL.Client.Tests.Common/StarWars/Types/CharacterInterface.cs +++ b/tests/GraphQL.Client.Tests.Common/StarWars/Types/CharacterInterface.cs @@ -9,8 +9,8 @@ public CharacterInterface() { Name = "Character"; - Field>("id").Description("The id of the character.").Resolve(context => context.Source.Id); - Field("name").Description("The name of the character.").Resolve(context => context.Source.Name); + Field>("id").Description("The id of the character."); + Field("name").Description("The name of the character."); Field>("friends"); Field>>("friendsConnection"); diff --git a/tests/GraphQL.Integration.Tests/Helpers/WebHostHelpers.cs b/tests/GraphQL.Integration.Tests/Helpers/WebHostHelpers.cs index e077fe10..9354f183 100644 --- a/tests/GraphQL.Integration.Tests/Helpers/WebHostHelpers.cs +++ b/tests/GraphQL.Integration.Tests/Helpers/WebHostHelpers.cs @@ -1,6 +1,5 @@ using GraphQL.Client.Abstractions.Websocket; using GraphQL.Client.Http; -using GraphQL.Client.Http.Websocket; using GraphQL.Client.Serializer.Newtonsoft; using IntegrationTestServer; diff --git a/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj b/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj index 901724ee..d0b222c1 100644 --- a/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj +++ b/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj @@ -6,9 +6,9 @@ - - - + + + diff --git a/tests/IntegrationTestServer/IntegrationTestServer.csproj b/tests/IntegrationTestServer/IntegrationTestServer.csproj index d26b2adb..ad1e5255 100644 --- a/tests/IntegrationTestServer/IntegrationTestServer.csproj +++ b/tests/IntegrationTestServer/IntegrationTestServer.csproj @@ -1,4 +1,4 @@ - + net7 @@ -11,7 +11,7 @@ - + From 3805cdd192982b125e3d81c4035c13f4c3e4e141 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Apr 2023 22:50:30 +0300 Subject: [PATCH 430/455] Bump GraphQL.NewtonsoftJson from 7.4.0 to 7.4.1 (#549) Bumps [GraphQL.NewtonsoftJson](https://github.com/graphql-dotnet/graphql-dotnet) from 7.4.0 to 7.4.1. - [Release notes](https://github.com/graphql-dotnet/graphql-dotnet/releases) - [Commits](https://github.com/graphql-dotnet/graphql-dotnet/compare/7.4.0...7.4.1) --- updated-dependencies: - dependency-name: GraphQL.NewtonsoftJson dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../GraphQL.Client.Serializer.Tests.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj b/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj index 2daa9c99..e79b1508 100644 --- a/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj +++ b/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj @@ -1,4 +1,4 @@ - + @@ -7,7 +7,7 @@ - + From 76755db59f73b5ac81bc77216ead0703d6d72bb9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Apr 2023 23:56:04 +0300 Subject: [PATCH 431/455] Bump GraphQL.SystemTextJson from 7.4.0 to 7.4.1 (#550) Bumps [GraphQL.SystemTextJson](https://github.com/graphql-dotnet/graphql-dotnet) from 7.4.0 to 7.4.1. - [Release notes](https://github.com/graphql-dotnet/graphql-dotnet/releases) - [Commits](https://github.com/graphql-dotnet/graphql-dotnet/compare/7.4.0...7.4.1) --- updated-dependencies: - dependency-name: GraphQL.SystemTextJson dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../GraphQL.Client.Serializer.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj b/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj index e79b1508..ed2da1ab 100644 --- a/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj +++ b/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj @@ -8,7 +8,7 @@ - + From aa87838f8bae99926aeb47f47cd338d0ecb232fd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Apr 2023 08:54:37 +0300 Subject: [PATCH 432/455] Bump GraphQL.MicrosoftDI from 7.4.0 to 7.4.1 (#552) Bumps [GraphQL.MicrosoftDI](https://github.com/graphql-dotnet/graphql-dotnet) from 7.4.0 to 7.4.1. - [Release notes](https://github.com/graphql-dotnet/graphql-dotnet/releases) - [Commits](https://github.com/graphql-dotnet/graphql-dotnet/compare/7.4.0...7.4.1) --- updated-dependencies: - dependency-name: GraphQL.MicrosoftDI dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../GraphQL.Client.LocalExecution.csproj | 4 ++-- tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj b/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj index 7971ef82..04261eb7 100644 --- a/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj +++ b/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj @@ -1,4 +1,4 @@ - + A GraphQL Client which executes the queries directly on a provided GraphQL schema using graphql-dotnet @@ -6,7 +6,7 @@ - + diff --git a/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj b/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj index d0b222c1..7220da61 100644 --- a/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj +++ b/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj @@ -6,7 +6,7 @@ - + From 7e1aa16336afeef4304727ec620a446943fa7aae Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Wed, 26 Apr 2023 11:05:08 +0300 Subject: [PATCH 433/455] Make GraphQLRequest.Extensions dictionary (#556) --- src/GraphQL.Client/GraphQLHttpRequest.cs | 6 ++++-- src/GraphQL.Primitives/GraphQLRequest.cs | 6 +++--- .../TestData/SerializeToStringTestData.cs | 4 ++++ 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/GraphQL.Client/GraphQLHttpRequest.cs b/src/GraphQL.Client/GraphQLHttpRequest.cs index 6d3a7633..9ff0c600 100644 --- a/src/GraphQL.Client/GraphQLHttpRequest.cs +++ b/src/GraphQL.Client/GraphQLHttpRequest.cs @@ -11,11 +11,13 @@ public GraphQLHttpRequest() { } - public GraphQLHttpRequest(string query, object? variables = null, string? operationName = null) : base(query, variables, operationName) + public GraphQLHttpRequest(string query, object? variables = null, string? operationName = null, Dictionary? extensions = null) + : base(query, variables, operationName, extensions) { } - public GraphQLHttpRequest(GraphQLRequest other) : base(other) + public GraphQLHttpRequest(GraphQLRequest other) + : base(other) { } diff --git a/src/GraphQL.Primitives/GraphQLRequest.cs b/src/GraphQL.Primitives/GraphQLRequest.cs index 59593d7b..f33ed88a 100644 --- a/src/GraphQL.Primitives/GraphQLRequest.cs +++ b/src/GraphQL.Primitives/GraphQLRequest.cs @@ -40,15 +40,15 @@ public object? Variables /// /// Represents the request extensions /// - public object? Extensions + public Dictionary? Extensions { - get => TryGetValue(EXTENSIONS_KEY, out object value) ? value : null; + get => TryGetValue(EXTENSIONS_KEY, out object value) && value is Dictionary d ? d : null; set => this[EXTENSIONS_KEY] = value; } public GraphQLRequest() { } - public GraphQLRequest(string query, object? variables = null, string? operationName = null, object? extensions = null) + public GraphQLRequest(string query, object? variables = null, string? operationName = null, Dictionary? extensions = null) { Query = query; Variables = variables; diff --git a/tests/GraphQL.Client.Serializer.Tests/TestData/SerializeToStringTestData.cs b/tests/GraphQL.Client.Serializer.Tests/TestData/SerializeToStringTestData.cs index e01c6335..85bc5e3b 100644 --- a/tests/GraphQL.Client.Serializer.Tests/TestData/SerializeToStringTestData.cs +++ b/tests/GraphQL.Client.Serializer.Tests/TestData/SerializeToStringTestData.cs @@ -10,6 +10,10 @@ public IEnumerator GetEnumerator() "{\"query\":\"simplequerystring\",\"variables\":null,\"operationName\":null,\"extensions\":null}", new GraphQLRequest("simple query string") }; + yield return new object[] { + "{\"query\":\"simplequerystring\",\"variables\":null,\"operationName\":null,\"extensions\":{\"a\":\"abc\",\"b\":true,\"c\":{\"d\":42}}}", + new GraphQLRequest("simple query string", extensions: new Dictionary { ["a"] = "abc", ["b"] = true, ["c"] = new Dictionary { ["d"] = 42 } }) + }; yield return new object[] { "{\"query\":\"simplequerystring\",\"variables\":{\"camelCaseProperty\":\"camelCase\",\"PascalCaseProperty\":\"PascalCase\"},\"operationName\":null,\"extensions\":null}", new GraphQLRequest("simple query string", new { camelCaseProperty = "camelCase", PascalCaseProperty = "PascalCase"}) From 8210ac36aa5fabedc24826497ce86ec5ec29796f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Apr 2023 13:06:34 +0300 Subject: [PATCH 434/455] Bump GraphQL from 7.4.0 to 7.4.1 (#551) Bumps [GraphQL](https://github.com/graphql-dotnet/graphql-dotnet) from 7.4.0 to 7.4.1. - [Release notes](https://github.com/graphql-dotnet/graphql-dotnet/releases) - [Commits](https://github.com/graphql-dotnet/graphql-dotnet/compare/7.4.0...7.4.1) --- updated-dependencies: - dependency-name: GraphQL dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../GraphQL.Client.Tests.Common.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj index 98393c8f..138f4c67 100644 --- a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj +++ b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj @@ -1,4 +1,4 @@ - + net7.0 @@ -7,7 +7,7 @@ - + From f1a5bf85aa45eb2ad2ed12fbcc3db425e999e3b0 Mon Sep 17 00:00:00 2001 From: Mikolaj Date: Mon, 8 May 2023 08:56:53 +0200 Subject: [PATCH 435/455] Improve default IsValidResponseToDeserialize implementation (#560) * change default implementation of response validation method to ensure correct response content type * add tests for the default validation method * remove unnecessary comments Co-authored-by: Ivan Maximov * accepted response types private, formatting * make DefaultIsValidResponseToDeserialize publicly accessible Co-authored-by: Ivan Maximov * AcceptedResponseContentTypes as static field --------- Co-authored-by: Ivan Maximov --- .../GraphQLHttpClientOptions.cs | 14 ++++++++-- .../DefaultValidationTest.cs | 28 +++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 tests/GraphQL.Client.Serializer.Tests/DefaultValidationTest.cs diff --git a/src/GraphQL.Client/GraphQLHttpClientOptions.cs b/src/GraphQL.Client/GraphQLHttpClientOptions.cs index 13a9c576..169260f0 100644 --- a/src/GraphQL.Client/GraphQLHttpClientOptions.cs +++ b/src/GraphQL.Client/GraphQLHttpClientOptions.cs @@ -61,9 +61,17 @@ public class GraphQLHttpClientOptions /// Note that compatible to the draft graphql-over-http spec GraphQL Server MAY return 4xx status codes (401/403, etc.) /// with well-formed GraphQL response containing errors collection. /// - public Func IsValidResponseToDeserialize { get; set; } = r => - // Why not application/json? See https://github.com/graphql/graphql-over-http/blob/main/spec/GraphQLOverHTTP.md#processing-the-response - r.IsSuccessStatusCode || r.StatusCode == HttpStatusCode.BadRequest || r.Content.Headers.ContentType?.MediaType == "application/graphql+json"; + public Func IsValidResponseToDeserialize { get; set; } = DefaultIsValidResponseToDeserialize; + + private static readonly IReadOnlyCollection _acceptedResponseContentTypes = new[] { "application/graphql+json", "application/json", "application/graphql-response+json" }; + + public static bool DefaultIsValidResponseToDeserialize(HttpResponseMessage r) + { + if (r.Content.Headers.ContentType?.MediaType != null && !_acceptedResponseContentTypes.Contains(r.Content.Headers.ContentType.MediaType)) + return false; + + return r.IsSuccessStatusCode || r.StatusCode == HttpStatusCode.BadRequest; + } /// /// This callback is called after successfully establishing a websocket connection but before any regular request is made. diff --git a/tests/GraphQL.Client.Serializer.Tests/DefaultValidationTest.cs b/tests/GraphQL.Client.Serializer.Tests/DefaultValidationTest.cs new file mode 100644 index 00000000..861b59b6 --- /dev/null +++ b/tests/GraphQL.Client.Serializer.Tests/DefaultValidationTest.cs @@ -0,0 +1,28 @@ +using System.Net; +using System.Net.Http.Headers; +using FluentAssertions; +using GraphQL.Client.Http; +using Xunit; + +namespace GraphQL.Client.Serializer.Tests; + +public class DefaultValidationTest +{ + [Theory] + [InlineData(HttpStatusCode.OK, "application/json", true)] + [InlineData(HttpStatusCode.OK, "application/graphql-response+json", true)] + [InlineData(HttpStatusCode.BadRequest, "application/json", true)] + [InlineData(HttpStatusCode.BadRequest, "text/html", false)] + [InlineData(HttpStatusCode.OK, "text/html", false)] + [InlineData(HttpStatusCode.Forbidden, "text/html", false)] + [InlineData(HttpStatusCode.Forbidden, "application/json", false)] + public void IsValidResponse_OkJson_True(HttpStatusCode statusCode, string mediaType, bool expectedResult) + { + var response = new HttpResponseMessage(statusCode); + response.Content.Headers.ContentType = new MediaTypeHeaderValue(mediaType); + + bool isValid = new GraphQLHttpClientOptions().IsValidResponseToDeserialize(response); + + isValid.Should().Be(expectedResult); + } +} From a5dfcd89928c494b449dec80498add1343e66257 Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Wed, 10 May 2023 01:24:53 +0300 Subject: [PATCH 436/455] Remove GraphQLHttpRequest.PreprocessHttpRequestMessage (#561) --- src/GraphQL.Client/GraphQLHttpRequest.cs | 11 ---------- .../QueryAndMutationTests/Base.cs | 22 ------------------- 2 files changed, 33 deletions(-) diff --git a/src/GraphQL.Client/GraphQLHttpRequest.cs b/src/GraphQL.Client/GraphQLHttpRequest.cs index 9ff0c600..c9d4e01a 100644 --- a/src/GraphQL.Client/GraphQLHttpRequest.cs +++ b/src/GraphQL.Client/GraphQLHttpRequest.cs @@ -1,5 +1,4 @@ using System.Net.Http.Headers; -using System.Runtime.Serialization; using System.Text; using GraphQL.Client.Abstractions; @@ -21,13 +20,6 @@ public GraphQLHttpRequest(GraphQLRequest other) { } - /// - /// Allows to preprocess a before it is sent, i.e. add custom headers - /// - [IgnoreDataMember] - [Obsolete("Inherit from GraphQLHttpRequest and override ToHttpRequestMessage() to customize the HttpRequestMessage. Will be removed in v4.0.0.")] - public Action PreprocessHttpRequestMessage { get; set; } = message => { }; - /// /// Creates a from this . /// Used by to convert GraphQL requests when sending them as regular HTTP requests. @@ -48,9 +40,6 @@ public virtual HttpRequestMessage ToHttpRequestMessage(GraphQLHttpClientOptions if (options.DefaultUserAgentRequestHeader != null) message.Headers.UserAgent.Add(options.DefaultUserAgentRequestHeader); -#pragma warning disable CS0618 // Type or member is obsolete - PreprocessHttpRequestMessage(message); -#pragma warning restore CS0618 // Type or member is obsolete return message; } } diff --git a/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs b/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs index bff54832..0b120ba4 100644 --- a/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs +++ b/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs @@ -163,28 +163,6 @@ query Human($id: String!){ Assert.Equal("Han Solo", queryResponse.Data.Human.Name); } - [Fact] - public async void PreprocessHttpRequestMessageIsCalled() - { - var callbackTester = new CallbackMonitor(); - var graphQLRequest = new GraphQLHttpRequest($"{{ human(id: \"1\") {{ name }} }}") - { -#pragma warning disable CS0618 // Type or member is obsolete - PreprocessHttpRequestMessage = callbackTester.Invoke -#pragma warning restore CS0618 // Type or member is obsolete - }; - - var expectedHeaders = new HttpRequestMessage().Headers; - expectedHeaders.UserAgent.Add(new ProductInfoHeaderValue("GraphQL.Client", typeof(GraphQLHttpClient).Assembly.GetName().Version.ToString())); - expectedHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/graphql-response+json")); - expectedHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - expectedHeaders.AcceptCharset.Add(new StringWithQualityHeaderValue("utf-8")); - var response = await StarWarsClient.SendQueryAsync(graphQLRequest, () => new { Human = new { Name = string.Empty } }); - callbackTester.Should().HaveBeenInvokedWithPayload().Which.Headers.Should().BeEquivalentTo(expectedHeaders); - Assert.Null(response.Errors); - Assert.Equal("Luke", response.Data.Human.Name); - } - [Fact] public async Task PostRequestCanBeCancelled() { From 42ffd7a56b1e186ad0ce3e9a749fd3792d3d29c5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 May 2023 19:43:09 +0300 Subject: [PATCH 437/455] Bump Microsoft.NET.Test.Sdk from 17.5.0 to 17.6.0 (#562) Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.5.0 to 17.6.0. - [Release notes](https://github.com/microsoft/vstest/releases) - [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md) - [Commits](https://github.com/microsoft/vstest/compare/v17.5.0...v17.6.0) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tests/tests.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tests.props b/tests/tests.props index 87ec1373..3c9038bf 100644 --- a/tests/tests.props +++ b/tests/tests.props @@ -7,7 +7,7 @@ - + From 344a3e9f2ff2d110d331d377c25611e0306ed042 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 May 2023 19:35:40 +0300 Subject: [PATCH 438/455] Bump coverlet.collector from 3.2.0 to 6.0.0 (#567) Bumps [coverlet.collector](https://github.com/coverlet-coverage/coverlet) from 3.2.0 to 6.0.0. - [Release notes](https://github.com/coverlet-coverage/coverlet/releases) - [Commits](https://github.com/coverlet-coverage/coverlet/compare/v3.2.0...v6.0.0) --- updated-dependencies: - dependency-name: coverlet.collector dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tests/tests.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tests.props b/tests/tests.props index 3c9038bf..f2765e66 100644 --- a/tests/tests.props +++ b/tests/tests.props @@ -10,7 +10,7 @@ - + all runtime;build;native;contentfiles;analyzers;buildtransitive From e92e7af15246ec6e57bb874a43e4ab6dd7ce89a3 Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Sat, 2 Sep 2023 06:47:04 +0300 Subject: [PATCH 439/455] Fix server test --- tests/GraphQL.Server.Test/Startup.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/GraphQL.Server.Test/Startup.cs b/tests/GraphQL.Server.Test/Startup.cs index 114ce5ff..04dddcdc 100644 --- a/tests/GraphQL.Server.Test/Startup.cs +++ b/tests/GraphQL.Server.Test/Startup.cs @@ -22,6 +22,7 @@ public void ConfigureServices(IServiceCollection services) { services.AddGraphQL(builder => builder .AddSchema() + .AddSystemTextJson() .UseApolloTracing(enableMetrics: true) .AddErrorInfoProvider(opt => opt.ExposeExceptionDetails = true) ); From 5436facadb3a82e434128fa3768f147e0b907d67 Mon Sep 17 00:00:00 2001 From: Joris van Eijden Date: Fri, 29 Sep 2023 09:46:40 +0200 Subject: [PATCH 440/455] Fix ImmutableConverter.cs (#572) * Update ImmutableConverter.cs Swap properties and parameters in applicability check of ImmutableConverter. This prevents properties from being lost when they are not all represented in the contructor as parameters. * Add unit test * Move test to base zo all serializers are covered --------- Co-authored-by: Joris van Eijden --- .../ImmutableConverter.cs | 33 +++++++++---------- .../BaseSerializerTest.cs | 25 ++++++++++++++ 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/src/GraphQL.Client.Serializer.SystemTextJson/ImmutableConverter.cs b/src/GraphQL.Client.Serializer.SystemTextJson/ImmutableConverter.cs index 96bad2c6..2087631d 100644 --- a/src/GraphQL.Client.Serializer.SystemTextJson/ImmutableConverter.cs +++ b/src/GraphQL.Client.Serializer.SystemTextJson/ImmutableConverter.cs @@ -19,31 +19,30 @@ public override bool CanConvert(Type typeToConvert) if (nullableUnderlyingType != null && nullableUnderlyingType.IsValueType) return false; - bool result; var constructors = typeToConvert.GetConstructors(BindingFlags.Public | BindingFlags.Instance); if (constructors.Length != 1) { - result = false; + return false; } - else + + var constructor = constructors[0]; + var parameters = constructor.GetParameters(); + + if (parameters.Length <= 0) { - var constructor = constructors[0]; - var parameters = constructor.GetParameters(); + return false; + } - if (parameters.Length > 0) - { - var properties = typeToConvert.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); - result = parameters - .Select(parameter => properties.Any(p => NameOfPropertyAndParameter.Matches(p.Name, parameter.Name, typeToConvert.IsAnonymous()))) - .All(hasMatchingProperty => hasMatchingProperty); - } - else - { - result = false; - } + var properties = typeToConvert.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + + if (properties.Length > parameters.Length) + { + return false; } - return result; + return properties + .Select(property => parameters.Any(parameter => NameOfPropertyAndParameter.Matches(property.Name, parameter.Name, typeToConvert.IsAnonymous()))) + .All(hasMatchingParameter => hasMatchingParameter); } public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) diff --git a/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs b/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs index ddb71471..0a7102a1 100644 --- a/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs +++ b/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs @@ -190,4 +190,29 @@ public void CanSerializeNullableStruct() action.Should().NotThrow(); } + + [Fact] + public async Task CanDeserializeObjectWithBothConstructorAndProperties() + { + // Arrange + const string jsonString = @"{ ""data"": { ""property1"": ""value1"", ""property2"": ""value2"" } }"; + var contentStream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(jsonString)); + + // Act + var result = await ClientSerializer.DeserializeFromUtf8StreamAsync(contentStream, default).ConfigureAwait(false); + + // Assert + result.Data.Property1.Should().Be("value1"); + result.Data.Property2.Should().Be("value2"); + } + + private class WithConstructorAndProperties + { + public WithConstructorAndProperties(string property2) + { + Property2 = property2; + } + public string? Property1 { get; set; } + public string Property2 { get; } + } } From d422bfdd0f5e3a1fba2af86c1aa9aaa41e28e495 Mon Sep 17 00:00:00 2001 From: Vivek Jayachandran <125931307+Vivek4Int@users.noreply.github.com> Date: Fri, 29 Sep 2023 20:48:19 +1300 Subject: [PATCH 441/455] Explicitly setting content header to avoid issues with some GraphQL servers. (#595) * Explicitly setting content header to avoid issues with some GraphQL servers. For instance synthetic GraphQL generated out of Azure APIM. --- src/GraphQL.Client/GraphQLHttpRequest.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/GraphQL.Client/GraphQLHttpRequest.cs b/src/GraphQL.Client/GraphQLHttpRequest.cs index c9d4e01a..d03b9f8e 100644 --- a/src/GraphQL.Client/GraphQLHttpRequest.cs +++ b/src/GraphQL.Client/GraphQLHttpRequest.cs @@ -35,7 +35,10 @@ public virtual HttpRequestMessage ToHttpRequestMessage(GraphQLHttpClientOptions }; message.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/graphql-response+json")); message.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - message.Headers.AcceptCharset.Add(new StringWithQualityHeaderValue("utf-8")); + message.Headers.AcceptCharset.Add(new StringWithQualityHeaderValue("utf-8")); + + // Explicitly setting content header to avoid issues with some GrahQL servers + message.Content.Headers.ContentType = new MediaTypeHeaderValue(options.MediaType); if (options.DefaultUserAgentRequestHeader != null) message.Headers.UserAgent.Add(options.DefaultUserAgentRequestHeader); From cfac737d07bca0a067488d62a6336361b7f2a6a8 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Thu, 23 Nov 2023 09:30:34 +0100 Subject: [PATCH 442/455] Fix Newtonsoft.Json MapConverter (#602) * extend test to cover issue * fix test * fix newtonsoft mapconverter * update gitversion * Try to fix breaking change regarding implicit using uf System.Net.Http * update sourcelink package to v 8.0.0 * rm package ref to sourcelink * add using System.Net.Http to GraphQLHttpWebSocket * upgrade net7 projects to net8 --- Directory.Build.targets | 10 +-- dotnet-tools.json | 2 +- .../GraphQL.Client.Example.csproj | 2 +- .../MapConverter.cs | 12 ++-- src/GraphQL.Client/GraphQLHttpClient.cs | 4 ++ .../GraphQLHttpClientOptions.cs | 4 ++ src/GraphQL.Client/GraphQLHttpRequest.cs | 4 ++ .../Websocket/GraphQLHttpWebSocket.cs | 4 ++ .../ConsistencyTests.cs | 64 ++++++++++++++----- .../GraphQL.Client.Serializer.Tests.csproj | 2 +- .../GraphQL.Client.Tests.Common.csproj | 2 +- .../GraphQL.Integration.Tests.csproj | 2 +- .../GraphQL.Primitives.Tests.csproj | 2 +- .../GraphQL.Server.Test.csproj | 2 +- .../IntegrationTestServer.csproj | 2 +- 15 files changed, 84 insertions(+), 34 deletions(-) diff --git a/Directory.Build.targets b/Directory.Build.targets index a75e6500..f09b31ba 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -6,7 +6,7 @@ true - + $(NoWarn);1591 @@ -33,10 +33,10 @@ - - + + all - runtime; build; native; contentfiles; analyzers; buildtransitive + runtime; build; native; contentfiles; analyzers diff --git a/dotnet-tools.json b/dotnet-tools.json index 7254cc09..e5146732 100644 --- a/dotnet-tools.json +++ b/dotnet-tools.json @@ -8,7 +8,7 @@ ] }, "gitversion.tool": { - "version": "5.10.3", + "version": "5.12.0", "commands": [ "dotnet-gitversion" ] diff --git a/examples/GraphQL.Client.Example/GraphQL.Client.Example.csproj b/examples/GraphQL.Client.Example/GraphQL.Client.Example.csproj index 28c2c457..07866dfc 100644 --- a/examples/GraphQL.Client.Example/GraphQL.Client.Example.csproj +++ b/examples/GraphQL.Client.Example/GraphQL.Client.Example.csproj @@ -2,7 +2,7 @@ Exe - net7 + net8 false diff --git a/src/GraphQL.Client.Serializer.Newtonsoft/MapConverter.cs b/src/GraphQL.Client.Serializer.Newtonsoft/MapConverter.cs index 90b22ed8..5bc53686 100644 --- a/src/GraphQL.Client.Serializer.Newtonsoft/MapConverter.cs +++ b/src/GraphQL.Client.Serializer.Newtonsoft/MapConverter.cs @@ -9,15 +9,15 @@ public override void WriteJson(JsonWriter writer, Map value, JsonSerializer seri throw new NotImplementedException( "This converter currently is only intended to be used to read a JSON object into a strongly-typed representation."); - public override Map ReadJson(JsonReader reader, Type objectType, Map existingValue, bool hasExistingValue, JsonSerializer serializer) + public override Map? ReadJson(JsonReader reader, Type objectType, Map existingValue, bool hasExistingValue, JsonSerializer serializer) { var rootToken = JToken.ReadFrom(reader); - if (rootToken is JObject) + return rootToken.Type switch { - return (Map)ReadDictionary(rootToken, new Map()); - } - else - throw new ArgumentException("This converter can only parse when the root element is a JSON Object."); + JTokenType.Object => (Map)ReadDictionary(rootToken, new Map()), + JTokenType.Null => null, + _ => throw new ArgumentException("This converter can only parse when the root element is a JSON Object.") + }; } private object? ReadToken(JToken? token) => diff --git a/src/GraphQL.Client/GraphQLHttpClient.cs b/src/GraphQL.Client/GraphQLHttpClient.cs index 7ffea50b..dd03334a 100644 --- a/src/GraphQL.Client/GraphQLHttpClient.cs +++ b/src/GraphQL.Client/GraphQLHttpClient.cs @@ -1,4 +1,8 @@ using System.Diagnostics; +#pragma warning disable IDE0005 +// see https://learn.microsoft.com/en-us/dotnet/core/compatibility/sdk/8.0/implicit-global-using-netfx +using System.Net.Http; +#pragma warning restore IDE0005 using GraphQL.Client.Abstractions; using GraphQL.Client.Abstractions.Websocket; using GraphQL.Client.Http.Websocket; diff --git a/src/GraphQL.Client/GraphQLHttpClientOptions.cs b/src/GraphQL.Client/GraphQLHttpClientOptions.cs index 169260f0..6c3b30d1 100644 --- a/src/GraphQL.Client/GraphQLHttpClientOptions.cs +++ b/src/GraphQL.Client/GraphQLHttpClientOptions.cs @@ -1,4 +1,8 @@ using System.Net; +#pragma warning disable IDE0005 +// see https://learn.microsoft.com/en-us/dotnet/core/compatibility/sdk/8.0/implicit-global-using-netfx +using System.Net.Http; +#pragma warning restore IDE0005 using System.Net.Http.Headers; using System.Net.WebSockets; using GraphQL.Client.Http.Websocket; diff --git a/src/GraphQL.Client/GraphQLHttpRequest.cs b/src/GraphQL.Client/GraphQLHttpRequest.cs index d03b9f8e..986f1dd5 100644 --- a/src/GraphQL.Client/GraphQLHttpRequest.cs +++ b/src/GraphQL.Client/GraphQLHttpRequest.cs @@ -1,3 +1,7 @@ +#pragma warning disable IDE0005 +// see https://learn.microsoft.com/en-us/dotnet/core/compatibility/sdk/8.0/implicit-global-using-netfx +using System.Net.Http; +#pragma warning restore IDE0005 using System.Net.Http.Headers; using System.Text; using GraphQL.Client.Abstractions; diff --git a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs index 32925567..4be55bc7 100644 --- a/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs +++ b/src/GraphQL.Client/Websocket/GraphQLHttpWebSocket.cs @@ -1,4 +1,8 @@ using System.Diagnostics; +#pragma warning disable IDE0005 +// see https://learn.microsoft.com/en-us/dotnet/core/compatibility/sdk/8.0/implicit-global-using-netfx +using System.Net.Http; +#pragma warning restore IDE0005 using System.Net.WebSockets; using System.Reactive; using System.Reactive.Disposables; diff --git a/tests/GraphQL.Client.Serializer.Tests/ConsistencyTests.cs b/tests/GraphQL.Client.Serializer.Tests/ConsistencyTests.cs index 200c0acf..7a3f8f55 100644 --- a/tests/GraphQL.Client.Serializer.Tests/ConsistencyTests.cs +++ b/tests/GraphQL.Client.Serializer.Tests/ConsistencyTests.cs @@ -9,10 +9,8 @@ namespace GraphQL.Client.Serializer.Tests; public class ConsistencyTests { - [Fact] - public void MapConvertersShouldBehaveConsistent() - { - const string json = @"{ + [Theory] + [InlineData(@"{ ""array"": [ ""some stuff"", ""something else"" @@ -27,7 +25,26 @@ public void MapConvertersShouldBehaveConsistent() {""number"": 1234.567}, {""number"": 567.8} ] - }"; + }")] + [InlineData("null")] + public void MapConvertersShouldBehaveConsistent(string json) + { + //const string json = @"{ + // ""array"": [ + // ""some stuff"", + // ""something else"" + // ], + // ""string"": ""this is a string"", + // ""boolean"": true, + // ""number"": 1234.567, + // ""nested object"": { + // ""prop1"": false + // }, + // ""arrayOfObjects"": [ + // {""number"": 1234.567}, + // {""number"": 567.8} + // ] + // }"; var newtonsoftSerializer = new NewtonsoftJsonSerializer(); var systemTextJsonSerializer = new SystemTextJsonSerializer(); @@ -45,16 +62,33 @@ public void MapConvertersShouldBehaveConsistent() .RespectingRuntimeTypes()); } - private void CompareMaps(Dictionary first, Dictionary second) + /// + /// Regression test for https://github.com/graphql-dotnet/graphql-client/issues/601 + /// + [Fact] + public void MapConvertersShouldBeAbleToDeserializeNullValues() { - foreach (var keyValuePair in first) - { - second.Should().ContainKey(keyValuePair.Key); - second[keyValuePair.Key].Should().BeOfType(keyValuePair.Value.GetType()); - if (keyValuePair.Value is Dictionary map) - CompareMaps(map, (Dictionary)second[keyValuePair.Key]); - else - keyValuePair.Value.Should().BeEquivalentTo(second[keyValuePair.Key]); - } + var newtonsoftSerializer = new NewtonsoftJsonSerializer(); + var systemTextJsonSerializer = new SystemTextJsonSerializer(); + string json = "null"; + + JsonConvert.DeserializeObject(json, newtonsoftSerializer.JsonSerializerSettings).Should().BeNull(); + System.Text.Json.JsonSerializer.Deserialize(json, systemTextJsonSerializer.Options).Should().BeNull(); + } + + private void CompareMaps(Dictionary? first, Dictionary? second) + { + if (first is null) + second.Should().BeNull(); + else + foreach (var keyValuePair in first) + { + second.Should().ContainKey(keyValuePair.Key); + second[keyValuePair.Key].Should().BeOfType(keyValuePair.Value.GetType()); + if (keyValuePair.Value is Dictionary map) + CompareMaps(map, (Dictionary)second[keyValuePair.Key]); + else + keyValuePair.Value.Should().BeEquivalentTo(second[keyValuePair.Key]); + } } } diff --git a/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj b/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj index ed2da1ab..7273de9a 100644 --- a/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj +++ b/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj @@ -3,7 +3,7 @@ - net7 + net8 diff --git a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj index 138f4c67..460fe724 100644 --- a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj +++ b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 false diff --git a/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj b/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj index d6f082fe..fdc033fa 100644 --- a/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj +++ b/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj @@ -3,7 +3,7 @@ - net7 + net8 diff --git a/tests/GraphQL.Primitives.Tests/GraphQL.Primitives.Tests.csproj b/tests/GraphQL.Primitives.Tests/GraphQL.Primitives.Tests.csproj index 235c025a..5210f214 100644 --- a/tests/GraphQL.Primitives.Tests/GraphQL.Primitives.Tests.csproj +++ b/tests/GraphQL.Primitives.Tests/GraphQL.Primitives.Tests.csproj @@ -3,7 +3,7 @@ - net7 + net8 diff --git a/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj b/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj index 7220da61..4b81bde3 100644 --- a/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj +++ b/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj @@ -1,7 +1,7 @@ - net7 + net8 false diff --git a/tests/IntegrationTestServer/IntegrationTestServer.csproj b/tests/IntegrationTestServer/IntegrationTestServer.csproj index ad1e5255..85b003c9 100644 --- a/tests/IntegrationTestServer/IntegrationTestServer.csproj +++ b/tests/IntegrationTestServer/IntegrationTestServer.csproj @@ -1,7 +1,7 @@  - net7 + net8 IntegrationTestServer.Program false From fbf37dfff2830e36a45296079bac27ec60fad550 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Thu, 23 Nov 2023 10:14:46 +0100 Subject: [PATCH 443/455] Upgrade dependencies (#603) * upgrade dependencies * fix code warnings --- .../GraphQL.Client.LocalExecution.csproj | 4 ++-- .../GraphQL.Client.Serializer.SystemTextJson.csproj | 2 +- .../ImmutableConverter.cs | 3 +++ src/GraphQL.Client/GraphQL.Client.csproj | 2 +- .../BaseSerializerTest.cs | 3 ++- .../GraphQL.Client.Serializer.Tests.csproj | 13 +++++++++++-- .../GraphQL.Client.Tests.Common.csproj | 4 ++-- .../GraphQL.Integration.Tests.csproj | 13 +++++++++++-- .../QueryAndMutationTests/Base.cs | 3 +-- .../GraphQL.Integration.Tests/UriExtensionTests.cs | 4 ++-- .../UserAgentHeaderTests.cs | 8 ++++---- .../WebsocketTests/Base.cs | 2 +- .../WebsocketTests/SystemTextJsonAutoNegotiate.cs | 2 +- .../SystemTextJsonGraphQLTransportWs.cs | 12 ++++++------ .../WebsocketTests/SystemTextJsonGraphQLWs.cs | 10 +++++----- .../GraphQL.Primitives.Tests.csproj | 9 +++++++++ .../GraphQL.Primitives.Tests/GraphQLRequestTest.cs | 11 ++++++----- .../GraphQL.Server.Test/GraphQL.Server.Test.csproj | 6 +++--- .../IntegrationTestServer.csproj | 6 +++--- 19 files changed, 74 insertions(+), 43 deletions(-) diff --git a/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj b/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj index 04261eb7..1e5575e0 100644 --- a/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj +++ b/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj @@ -6,8 +6,8 @@ - - + + diff --git a/src/GraphQL.Client.Serializer.SystemTextJson/GraphQL.Client.Serializer.SystemTextJson.csproj b/src/GraphQL.Client.Serializer.SystemTextJson/GraphQL.Client.Serializer.SystemTextJson.csproj index e49b975d..bd570172 100644 --- a/src/GraphQL.Client.Serializer.SystemTextJson/GraphQL.Client.Serializer.SystemTextJson.csproj +++ b/src/GraphQL.Client.Serializer.SystemTextJson/GraphQL.Client.Serializer.SystemTextJson.csproj @@ -6,7 +6,7 @@ - + diff --git a/src/GraphQL.Client.Serializer.SystemTextJson/ImmutableConverter.cs b/src/GraphQL.Client.Serializer.SystemTextJson/ImmutableConverter.cs index 2087631d..5a14d5ff 100644 --- a/src/GraphQL.Client.Serializer.SystemTextJson/ImmutableConverter.cs +++ b/src/GraphQL.Client.Serializer.SystemTextJson/ImmutableConverter.cs @@ -95,7 +95,10 @@ public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOp DefaultBufferSize = options.DefaultBufferSize, DictionaryKeyPolicy = options.DictionaryKeyPolicy, Encoder = options.Encoder, +#pragma warning disable SYSLIB0020 + // obsolete warning disabled to keep compatibility until deprecated field is removed IgnoreNullValues = options.IgnoreNullValues, +#pragma warning restore SYSLIB0020 IgnoreReadOnlyProperties = options.IgnoreReadOnlyProperties, MaxDepth = options.MaxDepth, PropertyNameCaseInsensitive = options.PropertyNameCaseInsensitive, diff --git a/src/GraphQL.Client/GraphQL.Client.csproj b/src/GraphQL.Client/GraphQL.Client.csproj index 35dc2e16..241e3bcc 100644 --- a/src/GraphQL.Client/GraphQL.Client.csproj +++ b/src/GraphQL.Client/GraphQL.Client.csproj @@ -15,7 +15,7 @@ - + diff --git a/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs b/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs index 0a7102a1..7cf5c3e1 100644 --- a/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs +++ b/tests/GraphQL.Client.Serializer.Tests/BaseSerializerTest.cs @@ -199,7 +199,8 @@ public async Task CanDeserializeObjectWithBothConstructorAndProperties() var contentStream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(jsonString)); // Act - var result = await ClientSerializer.DeserializeFromUtf8StreamAsync(contentStream, default).ConfigureAwait(false); + var result = await ClientSerializer + .DeserializeFromUtf8StreamAsync(contentStream, default); // Assert result.Data.Property1.Should().Be("value1"); diff --git a/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj b/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj index 7273de9a..edf3881f 100644 --- a/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj +++ b/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj @@ -7,8 +7,8 @@ - - + + @@ -19,4 +19,13 @@ + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + diff --git a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj index 460fe724..f199b89a 100644 --- a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj +++ b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj @@ -7,8 +7,8 @@ - - + + diff --git a/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj b/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj index fdc033fa..bb5b4616 100644 --- a/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj +++ b/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj @@ -7,8 +7,8 @@ - - + + @@ -18,4 +18,13 @@ + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + diff --git a/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs b/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs index 0b120ba4..cb25adc6 100644 --- a/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs +++ b/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs @@ -1,5 +1,4 @@ using System.Net; -using System.Net.Http.Headers; using FluentAssertions; using GraphQL.Client.Abstractions; using GraphQL.Client.Http; @@ -185,7 +184,7 @@ query Long { // unblock the query chatQuery.LongRunningQueryBlocker.Set(); // check execution time - request.Invoke().Result.Data.longRunning.Should().Be("finally returned"); + (await request.Invoke()).Data.longRunning.Should().Be("finally returned"); // reset stuff chatQuery.LongRunningQueryBlocker.Reset(); diff --git a/tests/GraphQL.Integration.Tests/UriExtensionTests.cs b/tests/GraphQL.Integration.Tests/UriExtensionTests.cs index 7e374578..80d032af 100644 --- a/tests/GraphQL.Integration.Tests/UriExtensionTests.cs +++ b/tests/GraphQL.Integration.Tests/UriExtensionTests.cs @@ -30,12 +30,12 @@ public void HasWebSocketSchemaTest(string url, bool result) [InlineData("ftp://this-url-cannot-be-converted.net", false, null)] // AppSync example [InlineData("wss://example1234567890000.appsync-realtime-api.us-west-2.amazonaws.com/graphql?header=123456789ABCDEF&payload=e30=", true, "wss://example1234567890000.appsync-realtime-api.us-west-2.amazonaws.com/graphql?header=123456789ABCDEF&payload=e30=")] - public void GetWebSocketUriTest(string input, bool canConvert, string result) + public void GetWebSocketUriTest(string input, bool canConvert, string? result) { var inputUri = new Uri(input); if (canConvert) { - inputUri.GetWebSocketUri().Should().BeEquivalentTo(new Uri(result)); + inputUri.GetWebSocketUri().Should().BeEquivalentTo(new Uri(result!)); } else { diff --git a/tests/GraphQL.Integration.Tests/UserAgentHeaderTests.cs b/tests/GraphQL.Integration.Tests/UserAgentHeaderTests.cs index bf631be5..ba5b7bbf 100644 --- a/tests/GraphQL.Integration.Tests/UserAgentHeaderTests.cs +++ b/tests/GraphQL.Integration.Tests/UserAgentHeaderTests.cs @@ -17,7 +17,7 @@ public UserAgentHeaderTests(SystemTextJsonAutoNegotiateServerTestFixture fixture Fixture = fixture; } - public async Task InitializeAsync() => await Fixture.CreateServer().ConfigureAwait(false); + public async Task InitializeAsync() => await Fixture.CreateServer(); public Task DisposeAsync() { @@ -32,7 +32,7 @@ public async void Can_set_custom_user_agent() ChatClient = Fixture.GetChatClient(options => options.DefaultUserAgentRequestHeader = ProductInfoHeaderValue.Parse(userAgent)); var graphQLRequest = new GraphQLRequest("query clientUserAgent { clientUserAgent }"); - var response = await ChatClient.SendQueryAsync(graphQLRequest, () => new { clientUserAgent = string.Empty }).ConfigureAwait(false); + var response = await ChatClient.SendQueryAsync(graphQLRequest, () => new { clientUserAgent = string.Empty }); response.Errors.Should().BeNull(); response.Data.clientUserAgent.Should().Be(userAgent); @@ -48,7 +48,7 @@ public async void Default_user_agent_is_set_as_expected() ChatClient = Fixture.GetChatClient(); var graphQLRequest = new GraphQLRequest("query clientUserAgent { clientUserAgent }"); - var response = await ChatClient.SendQueryAsync(graphQLRequest, () => new { clientUserAgent = string.Empty }).ConfigureAwait(false); + var response = await ChatClient.SendQueryAsync(graphQLRequest, () => new { clientUserAgent = string.Empty }); response.Errors.Should().BeNull(); response.Data.clientUserAgent.Should().Be(expectedUserAgent); @@ -60,7 +60,7 @@ public async void No_Default_user_agent_if_set_to_null() ChatClient = Fixture.GetChatClient(options => options.DefaultUserAgentRequestHeader = null); var graphQLRequest = new GraphQLRequest("query clientUserAgent { clientUserAgent }"); - var response = await ChatClient.SendQueryAsync(graphQLRequest, () => new { clientUserAgent = string.Empty }).ConfigureAwait(false); + var response = await ChatClient.SendQueryAsync(graphQLRequest, () => new { clientUserAgent = string.Empty }); response.Errors.Should().HaveCount(1); response.Errors[0].Message.Should().Be("user agent header not set"); diff --git a/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs b/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs index e16a4a38..98d4226d 100644 --- a/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs +++ b/tests/GraphQL.Integration.Tests/WebsocketTests/Base.cs @@ -121,7 +121,7 @@ query Long { // unblock the query chatQuery.LongRunningQueryBlocker.Set(); // check execution time - request.Invoke().Result.Data.longRunning.Should().Be("finally returned"); + (await request.Invoke()).Data.longRunning.Should().Be("finally returned"); // reset stuff chatQuery.LongRunningQueryBlocker.Reset(); diff --git a/tests/GraphQL.Integration.Tests/WebsocketTests/SystemTextJsonAutoNegotiate.cs b/tests/GraphQL.Integration.Tests/WebsocketTests/SystemTextJsonAutoNegotiate.cs index 0090e307..e6d72c90 100644 --- a/tests/GraphQL.Integration.Tests/WebsocketTests/SystemTextJsonAutoNegotiate.cs +++ b/tests/GraphQL.Integration.Tests/WebsocketTests/SystemTextJsonAutoNegotiate.cs @@ -15,7 +15,7 @@ public SystemTextJsonAutoNegotiate(ITestOutputHelper output, SystemTextJsonAutoN [Fact] public async Task WebSocketProtocolIsAutoNegotiated() { - await ChatClient.InitializeWebsocketConnection().ConfigureAwait(false); + await ChatClient.InitializeWebsocketConnection(); ChatClient.WebSocketSubProtocol.Should().Be(WebSocketProtocols.GRAPHQL_TRANSPORT_WS); } } diff --git a/tests/GraphQL.Integration.Tests/WebsocketTests/SystemTextJsonGraphQLTransportWs.cs b/tests/GraphQL.Integration.Tests/WebsocketTests/SystemTextJsonGraphQLTransportWs.cs index dfba089e..50fc92a0 100644 --- a/tests/GraphQL.Integration.Tests/WebsocketTests/SystemTextJsonGraphQLTransportWs.cs +++ b/tests/GraphQL.Integration.Tests/WebsocketTests/SystemTextJsonGraphQLTransportWs.cs @@ -15,21 +15,21 @@ public SystemTextJsonGraphQLTransportWs(ITestOutputHelper output, SystemTextJson [Fact] public async void Sending_a_pong_message_should_not_throw() { - await ChatClient.InitializeWebsocketConnection().ConfigureAwait(false); + await ChatClient.InitializeWebsocketConnection(); - await ChatClient.Awaiting(client => client.SendPongAsync(null)).Should().NotThrowAsync().ConfigureAwait(false); - await ChatClient.Awaiting(client => client.SendPongAsync("some payload")).Should().NotThrowAsync().ConfigureAwait(false); + await ChatClient.Awaiting(client => client.SendPongAsync(null)).Should().NotThrowAsync(); + await ChatClient.Awaiting(client => client.SendPongAsync("some payload")).Should().NotThrowAsync(); } [Fact] public async void Sending_a_ping_message_should_result_in_a_pong_message_from_the_server() { - await ChatClient.InitializeWebsocketConnection().ConfigureAwait(false); + await ChatClient.InitializeWebsocketConnection(); using var pongObserver = ChatClient.PongStream.Observe(); - await ChatClient.Awaiting(client => client.SendPingAsync(null)).Should().NotThrowAsync().ConfigureAwait(false); + await ChatClient.Awaiting(client => client.SendPingAsync(null)).Should().NotThrowAsync(); - await pongObserver.Should().PushAsync(1, TimeSpan.FromSeconds(1), "because the server was pinged by the client").ConfigureAwait(false); + await pongObserver.Should().PushAsync(1, TimeSpan.FromSeconds(1), "because the server was pinged by the client"); } } diff --git a/tests/GraphQL.Integration.Tests/WebsocketTests/SystemTextJsonGraphQLWs.cs b/tests/GraphQL.Integration.Tests/WebsocketTests/SystemTextJsonGraphQLWs.cs index 963f8292..b1efe0b1 100644 --- a/tests/GraphQL.Integration.Tests/WebsocketTests/SystemTextJsonGraphQLWs.cs +++ b/tests/GraphQL.Integration.Tests/WebsocketTests/SystemTextJsonGraphQLWs.cs @@ -14,25 +14,25 @@ public SystemTextJsonGraphQLWs(ITestOutputHelper output, SystemTextJsonGraphQLWs [Fact] public async void Sending_a_ping_message_should_throw_not_supported_exception() { - await ChatClient.InitializeWebsocketConnection().ConfigureAwait(false); + await ChatClient.InitializeWebsocketConnection(); await ChatClient.Awaiting(client => client.SendPingAsync(null)) - .Should().ThrowAsync().ConfigureAwait(false); + .Should().ThrowAsync(); } [Fact] public async void Sending_a_pong_message_should_throw_not_supported_exception() { - await ChatClient.InitializeWebsocketConnection().ConfigureAwait(false); + await ChatClient.InitializeWebsocketConnection(); await ChatClient.Awaiting(client => client.SendPongAsync(null)) - .Should().ThrowAsync().ConfigureAwait(false); + .Should().ThrowAsync(); } [Fact] public async void Subscribing_to_the_pong_stream_should_throw_not_supported_exception() { - await ChatClient.InitializeWebsocketConnection().ConfigureAwait(false); + await ChatClient.InitializeWebsocketConnection(); ChatClient.Invoking(client => client.PongStream.Subscribe()) .Should().Throw(); diff --git a/tests/GraphQL.Primitives.Tests/GraphQL.Primitives.Tests.csproj b/tests/GraphQL.Primitives.Tests/GraphQL.Primitives.Tests.csproj index 5210f214..ffe7c0a6 100644 --- a/tests/GraphQL.Primitives.Tests/GraphQL.Primitives.Tests.csproj +++ b/tests/GraphQL.Primitives.Tests/GraphQL.Primitives.Tests.csproj @@ -10,4 +10,13 @@ + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + diff --git a/tests/GraphQL.Primitives.Tests/GraphQLRequestTest.cs b/tests/GraphQL.Primitives.Tests/GraphQLRequestTest.cs index b56e6376..45560652 100644 --- a/tests/GraphQL.Primitives.Tests/GraphQLRequestTest.cs +++ b/tests/GraphQL.Primitives.Tests/GraphQLRequestTest.cs @@ -1,3 +1,4 @@ +using FluentAssertions; using Xunit; namespace GraphQL.Primitives.Tests; @@ -26,7 +27,7 @@ public void ConstructorExtendedFact() public void Equality1Fact() { var graphQLRequest = new GraphQLRequest("{hero{name}}"); - Assert.Equal(graphQLRequest, graphQLRequest); + graphQLRequest.Equals(graphQLRequest).Should().BeTrue(); } [Fact] @@ -34,7 +35,7 @@ public void Equality2Fact() { var graphQLRequest1 = new GraphQLRequest("{hero{name}}"); var graphQLRequest2 = new GraphQLRequest("{hero{name}}"); - Assert.Equal(graphQLRequest1, graphQLRequest2); + graphQLRequest1.Equals(graphQLRequest2).Should().BeTrue(); } [Fact] @@ -42,7 +43,7 @@ public void Equality3Fact() { var graphQLRequest1 = new GraphQLRequest("{hero{name}}", new { varName = "varValue" }, "operationName"); var graphQLRequest2 = new GraphQLRequest("{hero{name}}", new { varName = "varValue" }, "operationName"); - Assert.Equal(graphQLRequest1, graphQLRequest2); + graphQLRequest1.Equals(graphQLRequest2).Should().BeTrue(); } [Fact] @@ -66,7 +67,7 @@ public void InEquality1Fact() { var graphQLRequest1 = new GraphQLRequest("{hero{name1}}"); var graphQLRequest2 = new GraphQLRequest("{hero{name2}}"); - Assert.NotEqual(graphQLRequest1, graphQLRequest2); + graphQLRequest1.Equals(graphQLRequest2).Should().BeFalse(); } [Fact] @@ -74,7 +75,7 @@ public void InEquality2Fact() { var graphQLRequest1 = new GraphQLRequest("{hero{name}}", new { varName = "varValue1" }, "operationName"); var graphQLRequest2 = new GraphQLRequest("{hero{name}}", new { varName = "varValue2" }, "operationName"); - Assert.NotEqual(graphQLRequest1, graphQLRequest2); + graphQLRequest1.Equals(graphQLRequest2).Should().BeFalse(); } [Fact] diff --git a/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj b/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj index 4b81bde3..00d8126c 100644 --- a/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj +++ b/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj @@ -6,9 +6,9 @@ - - - + + + diff --git a/tests/IntegrationTestServer/IntegrationTestServer.csproj b/tests/IntegrationTestServer/IntegrationTestServer.csproj index 85b003c9..66d8071d 100644 --- a/tests/IntegrationTestServer/IntegrationTestServer.csproj +++ b/tests/IntegrationTestServer/IntegrationTestServer.csproj @@ -11,9 +11,9 @@ - - - + + + From 3e062755e952bbdc20b3158e17ecb131214c6103 Mon Sep 17 00:00:00 2001 From: zooeywm Date: Wed, 20 Mar 2024 19:39:42 +0800 Subject: [PATCH 444/455] Add new often used constructor (#627) --- src/GraphQL.Client/GraphQLHttpClient.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/GraphQL.Client/GraphQLHttpClient.cs b/src/GraphQL.Client/GraphQLHttpClient.cs index dd03334a..10076b8a 100644 --- a/src/GraphQL.Client/GraphQLHttpClient.cs +++ b/src/GraphQL.Client/GraphQLHttpClient.cs @@ -71,6 +71,15 @@ public GraphQLHttpClient(GraphQLHttpClientOptions options, IGraphQLWebsocketJson _lazyHttpWebSocket = new Lazy(CreateGraphQLHttpWebSocket); } + public GraphQLHttpClient(Action configure, IGraphQLWebsocketJsonSerializer serializer, HttpClient httpClient) + : this(configure.New(), serializer, httpClient) { } + + public GraphQLHttpClient(Uri endPoint, IGraphQLWebsocketJsonSerializer serializer, HttpClient httpClient) + : this(o => o.EndPoint = endPoint, serializer, httpClient) { } + + public GraphQLHttpClient(string endPoint, IGraphQLWebsocketJsonSerializer serializer, HttpClient httpClient) + : this(new Uri(endPoint), serializer, httpClient) { } + #endregion #region IGraphQLClient From 875520a3804e6a70967ec94fd809512e9772980b Mon Sep 17 00:00:00 2001 From: Adam Williams Date: Tue, 16 Apr 2024 09:14:35 +0100 Subject: [PATCH 445/455] Address #630 (#631) * Address #630 * Multi-framework build for primitives project * PR feedback: extension methods --- .../GraphQL.Client.Abstractions.csproj | 2 +- .../GraphQLClientExtensions.cs | 6 ++++-- .../GraphQL.Primitives.csproj | 2 +- src/GraphQL.Primitives/GraphQLRequest.cs | 5 ++++- src/GraphQL.Primitives/StringSyntaxAttribute.cs | 17 +++++++++++++++++ 5 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 src/GraphQL.Primitives/StringSyntaxAttribute.cs diff --git a/src/GraphQL.Client.Abstractions/GraphQL.Client.Abstractions.csproj b/src/GraphQL.Client.Abstractions/GraphQL.Client.Abstractions.csproj index 58f116f2..a18177ca 100644 --- a/src/GraphQL.Client.Abstractions/GraphQL.Client.Abstractions.csproj +++ b/src/GraphQL.Client.Abstractions/GraphQL.Client.Abstractions.csproj @@ -2,7 +2,7 @@ Abstractions for GraphQL.Client - netstandard2.0 + netstandard2.0;net7.0;net8.0 diff --git a/src/GraphQL.Client.Abstractions/GraphQLClientExtensions.cs b/src/GraphQL.Client.Abstractions/GraphQLClientExtensions.cs index e27d1a9a..cce6db1a 100644 --- a/src/GraphQL.Client.Abstractions/GraphQLClientExtensions.cs +++ b/src/GraphQL.Client.Abstractions/GraphQLClientExtensions.cs @@ -1,9 +1,11 @@ +using System.Diagnostics.CodeAnalysis; + namespace GraphQL.Client.Abstractions; public static class GraphQLClientExtensions { public static Task> SendQueryAsync(this IGraphQLClient client, - string query, object? variables = null, + [StringSyntax("GraphQL")] string query, object? variables = null, string? operationName = null, Func defineResponseType = null, CancellationToken cancellationToken = default) { _ = defineResponseType; @@ -12,7 +14,7 @@ public static Task> SendQueryAsync(this IG } public static Task> SendMutationAsync(this IGraphQLClient client, - string query, object? variables = null, + [StringSyntax("GraphQL")] string query, object? variables = null, string? operationName = null, Func defineResponseType = null, CancellationToken cancellationToken = default) { _ = defineResponseType; diff --git a/src/GraphQL.Primitives/GraphQL.Primitives.csproj b/src/GraphQL.Primitives/GraphQL.Primitives.csproj index 676e6fe8..b49b7360 100644 --- a/src/GraphQL.Primitives/GraphQL.Primitives.csproj +++ b/src/GraphQL.Primitives/GraphQL.Primitives.csproj @@ -3,7 +3,7 @@ GraphQL basic types GraphQL - netstandard2.0 + netstandard2.0;net7.0;net8.0 diff --git a/src/GraphQL.Primitives/GraphQLRequest.cs b/src/GraphQL.Primitives/GraphQLRequest.cs index f33ed88a..7aecb656 100644 --- a/src/GraphQL.Primitives/GraphQLRequest.cs +++ b/src/GraphQL.Primitives/GraphQLRequest.cs @@ -1,3 +1,5 @@ +using System.Diagnostics.CodeAnalysis; + namespace GraphQL; /// @@ -13,6 +15,7 @@ public class GraphQLRequest : Dictionary, IEquatable /// The Query /// + [StringSyntax("GraphQL")] public string Query { get => TryGetValue(QUERY_KEY, out object value) ? (string)value : null; @@ -48,7 +51,7 @@ public object? Variables public GraphQLRequest() { } - public GraphQLRequest(string query, object? variables = null, string? operationName = null, Dictionary? extensions = null) + public GraphQLRequest([StringSyntax("GraphQL")] string query, object? variables = null, string? operationName = null, Dictionary? extensions = null) { Query = query; Variables = variables; diff --git a/src/GraphQL.Primitives/StringSyntaxAttribute.cs b/src/GraphQL.Primitives/StringSyntaxAttribute.cs new file mode 100644 index 00000000..8ea74c0e --- /dev/null +++ b/src/GraphQL.Primitives/StringSyntaxAttribute.cs @@ -0,0 +1,17 @@ +#if !NET7_0_OR_GREATER + +namespace System.Diagnostics.CodeAnalysis; + +/// +/// Stub +/// +public sealed class StringSyntaxAttribute : Attribute +{ + public const string CompositeFormat = nameof(CompositeFormat); + + public StringSyntaxAttribute(string syntax) + { + } + +} +#endif From 6d1c0109d3085a2e992dca6fb032e7c3477b81d3 Mon Sep 17 00:00:00 2001 From: Kenneth Tubman Date: Tue, 16 Apr 2024 05:06:54 -0400 Subject: [PATCH 446/455] Removed reference to GraphQL.Client.Serializer.Newtonsoft in GraphQL.Client.LocalExecution (#633) --- .../GraphQL.Client.LocalExecution.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj b/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj index 1e5575e0..81bc57c3 100644 --- a/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj +++ b/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj @@ -12,7 +12,6 @@ - From 6681418dd19657bcbc808a7858190da97e410930 Mon Sep 17 00:00:00 2001 From: Kim Lukas Bechtold <51699215+kimlukas@users.noreply.github.com> Date: Mon, 22 Apr 2024 08:39:32 +0200 Subject: [PATCH 447/455] Waiting when disposing till the stop message was sent (#635) Co-authored-by: kim --- .../Websocket/GraphQLTransportWSProtocolHandler.cs | 4 ++-- src/GraphQL.Client/Websocket/GraphQLWSProtocolHandler.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/GraphQL.Client/Websocket/GraphQLTransportWSProtocolHandler.cs b/src/GraphQL.Client/Websocket/GraphQLTransportWSProtocolHandler.cs index 1ac5ffeb..2f3b28b0 100644 --- a/src/GraphQL.Client/Websocket/GraphQLTransportWSProtocolHandler.cs +++ b/src/GraphQL.Client/Websocket/GraphQLTransportWSProtocolHandler.cs @@ -116,7 +116,7 @@ public IObservable> CreateSubscriptionObservable + Disposable.Create(() => { Debug.WriteLine($"disposing subscription {startRequest.Id}, websocket state is '{_webSocketHandler.WebSocketState}'"); // only try to send close request on open websocket @@ -126,7 +126,7 @@ public IObservable> CreateSubscriptionObservable> CreateSubscriptionObservable + Disposable.Create(() => { Debug.WriteLine($"disposing subscription {startRequest.Id}, websocket state is '{_webSocketHandler.WebSocketState}'"); // only try to send close request on open websocket @@ -111,7 +111,7 @@ public IObservable> CreateSubscriptionObservable Date: Mon, 22 Apr 2024 07:26:04 +0000 Subject: [PATCH 448/455] Bump actions/upload-artifact from 3 to 4 (#610) --- .github/workflows/branches.yml | 2 +- .github/workflows/master.yml | 2 +- .github/workflows/publish.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/branches.yml b/.github/workflows/branches.yml index 494f1c75..d2e02ead 100644 --- a/.github/workflows/branches.yml +++ b/.github/workflows/branches.yml @@ -48,7 +48,7 @@ jobs: - name: Create NuGet packages run: dotnet pack -c Release --no-restore --no-build -o nupkg - name: Upload nuget packages - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: nupkg path: nupkg diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 3fc48c41..638d0141 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -49,7 +49,7 @@ jobs: - name: Create NuGet packages run: dotnet pack -c Release --no-restore --no-build -o nupkg - name: Upload nuget packages as artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: nupkg path: nupkg diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a7ebfb2e..d882f674 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -51,7 +51,7 @@ jobs: - name: Create NuGet packages run: dotnet pack -c Release --no-restore --no-build -o nupkg - name: Upload nuget packages as artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: nupkg path: nupkg From 28f9d6865efb4b2436b46b0ee1df0964321c4603 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Apr 2024 09:30:47 +0200 Subject: [PATCH 449/455] Bump actions/setup-dotnet from 3 to 4 (#608) Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 3 to 4. - [Release notes](https://github.com/actions/setup-dotnet/releases) - [Commits](https://github.com/actions/setup-dotnet/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/setup-dotnet dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/branches.yml | 2 +- .github/workflows/master.yml | 2 +- .github/workflows/publish.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/branches.yml b/.github/workflows/branches.yml index d2e02ead..9b0c6169 100644 --- a/.github/workflows/branches.yml +++ b/.github/workflows/branches.yml @@ -22,7 +22,7 @@ jobs: with: fetch-depth: 0 - name: Setup .NET SDK - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: | 7.0.x diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 638d0141..63015235 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -21,7 +21,7 @@ jobs: with: fetch-depth: 0 - name: Setup .NET SDK - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: "7.0.x" source-url: https://nuget.pkg.github.com/graphql-dotnet/index.json diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index d882f674..0abace42 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -25,7 +25,7 @@ jobs: echo github.ref: ${{ github.ref }} exit 1 - name: Setup .NET SDK - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: "7.0.x" source-url: https://api.nuget.org/v3/index.json From 26abe3e99498eaa4d06ba174e3be25384f77544d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Apr 2024 09:31:39 +0200 Subject: [PATCH 450/455] Bump actions/checkout from 3 to 4 (#606) Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/branches.yml | 2 +- .github/workflows/master.yml | 2 +- .github/workflows/publish.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/branches.yml b/.github/workflows/branches.yml index 9b0c6169..52a0b4c1 100644 --- a/.github/workflows/branches.yml +++ b/.github/workflows/branches.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup .NET SDK diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 63015235..4803f900 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup .NET SDK diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 0abace42..60978cdf 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Check github.ref starts with 'refs/tags/' From 95d3476b0dea110c30c9f5e82788ab2b262078bc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Apr 2024 09:32:41 +0200 Subject: [PATCH 451/455] Bump actions/github-script from 6 to 7 (#605) Bumps [actions/github-script](https://github.com/actions/github-script) from 6 to 7. - [Release notes](https://github.com/actions/github-script/releases) - [Commits](https://github.com/actions/github-script/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/github-script dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 60978cdf..eb5df29b 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -58,7 +58,7 @@ jobs: - name: Publish Nuget packages to Nuget registry run: dotnet nuget push "nupkg/*" -k ${{secrets.NUGET_API_KEY}} - name: Upload Nuget packages as release artifacts - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: github-token: ${{secrets.GITHUB_TOKEN}} script: | From 79b0161e611829ea563671b2d7972f8bf803d900 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Mon, 22 Apr 2024 09:36:09 +0200 Subject: [PATCH 452/455] Prepare Release 6.0.4 (#634) * add stringsyntaxattribute to constructor of GraphQLHttpRequest, ad "QL" abbreviation to resharper dictionary * disable compiler warnings in stub StringSyntaxAttribute for netstandard2.0 * upgrade dependencies * use dotnet 8.0 for gh workflows --- .github/workflows/branches.yml | 2 +- .github/workflows/master.yml | 2 +- .github/workflows/publish.yml | 2 +- GraphQL.Client.sln | 1 + GraphQL.Client.sln.DotSettings | 2 + .../GraphQL.Client.LocalExecution.csproj | 2 +- ...QL.Client.Serializer.SystemTextJson.csproj | 2 +- src/GraphQL.Client/GraphQLHttpRequest.cs | 99 ++++++++++--------- .../StringSyntaxAttribute.cs | 10 +- .../GraphQL.Client.Serializer.Tests.csproj | 14 ++- .../GraphQL.Client.Tests.Common.csproj | 2 +- .../StarWars/Types/DroidType.cs | 3 +- .../StarWars/Types/HumanType.cs | 3 +- .../GraphQL.Integration.Tests.csproj | 12 ++- .../GraphQL.Primitives.Tests.csproj | 10 +- .../GraphQL.Server.Test.csproj | 6 +- .../IntegrationTestServer.csproj | 2 +- 17 files changed, 96 insertions(+), 78 deletions(-) create mode 100644 GraphQL.Client.sln.DotSettings diff --git a/.github/workflows/branches.yml b/.github/workflows/branches.yml index 52a0b4c1..8ae6f0f8 100644 --- a/.github/workflows/branches.yml +++ b/.github/workflows/branches.yml @@ -25,7 +25,7 @@ jobs: uses: actions/setup-dotnet@v4 with: dotnet-version: | - 7.0.x + 8.0.x - name: Restore dotnet tools run: dotnet tool restore - name: Fetch complete repository including tags diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 4803f900..b26156bb 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -23,7 +23,7 @@ jobs: - name: Setup .NET SDK uses: actions/setup-dotnet@v4 with: - dotnet-version: "7.0.x" + dotnet-version: "8.0.x" source-url: https://nuget.pkg.github.com/graphql-dotnet/index.json env: NUGET_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index eb5df29b..bbb89933 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -27,7 +27,7 @@ jobs: - name: Setup .NET SDK uses: actions/setup-dotnet@v4 with: - dotnet-version: "7.0.x" + dotnet-version: "8.0.x" source-url: https://api.nuget.org/v3/index.json env: NUGET_AUTH_TOKEN: ${{secrets.NUGET_API_KEY}} diff --git a/GraphQL.Client.sln b/GraphQL.Client.sln index 89782ecc..af2fd372 100644 --- a/GraphQL.Client.sln +++ b/GraphQL.Client.sln @@ -32,6 +32,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{ ProjectSection(SolutionItems) = preProject .github\workflows\branches.yml = .github\workflows\branches.yml .github\workflows\master.yml = .github\workflows\master.yml + .github\workflows\publish.yml = .github\workflows\publish.yml EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphQL.Primitives", "src\GraphQL.Primitives\GraphQL.Primitives.csproj", "{87FC440E-6A4D-47D8-9EB2-416FC31CC4A6}" diff --git a/GraphQL.Client.sln.DotSettings b/GraphQL.Client.sln.DotSettings new file mode 100644 index 00000000..9e5ec22f --- /dev/null +++ b/GraphQL.Client.sln.DotSettings @@ -0,0 +1,2 @@ + + QL \ No newline at end of file diff --git a/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj b/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj index 81bc57c3..687cc656 100644 --- a/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj +++ b/src/GraphQL.Client.LocalExecution/GraphQL.Client.LocalExecution.csproj @@ -6,7 +6,7 @@ - + diff --git a/src/GraphQL.Client.Serializer.SystemTextJson/GraphQL.Client.Serializer.SystemTextJson.csproj b/src/GraphQL.Client.Serializer.SystemTextJson/GraphQL.Client.Serializer.SystemTextJson.csproj index bd570172..2a436c50 100644 --- a/src/GraphQL.Client.Serializer.SystemTextJson/GraphQL.Client.Serializer.SystemTextJson.csproj +++ b/src/GraphQL.Client.Serializer.SystemTextJson/GraphQL.Client.Serializer.SystemTextJson.csproj @@ -6,7 +6,7 @@ - + diff --git a/src/GraphQL.Client/GraphQLHttpRequest.cs b/src/GraphQL.Client/GraphQLHttpRequest.cs index 986f1dd5..b89d1814 100644 --- a/src/GraphQL.Client/GraphQLHttpRequest.cs +++ b/src/GraphQL.Client/GraphQLHttpRequest.cs @@ -1,52 +1,53 @@ -#pragma warning disable IDE0005 -// see https://learn.microsoft.com/en-us/dotnet/core/compatibility/sdk/8.0/implicit-global-using-netfx -using System.Net.Http; -#pragma warning restore IDE0005 -using System.Net.Http.Headers; -using System.Text; -using GraphQL.Client.Abstractions; - -namespace GraphQL.Client.Http; - -public class GraphQLHttpRequest : GraphQLRequest -{ - public GraphQLHttpRequest() - { - } - - public GraphQLHttpRequest(string query, object? variables = null, string? operationName = null, Dictionary? extensions = null) - : base(query, variables, operationName, extensions) - { - } - - public GraphQLHttpRequest(GraphQLRequest other) - : base(other) - { - } - - /// - /// Creates a from this . - /// Used by to convert GraphQL requests when sending them as regular HTTP requests. - /// - /// the passed from - /// the passed from - /// - public virtual HttpRequestMessage ToHttpRequestMessage(GraphQLHttpClientOptions options, IGraphQLJsonSerializer serializer) - { - var message = new HttpRequestMessage(HttpMethod.Post, options.EndPoint) - { - Content = new StringContent(serializer.SerializeToString(this), Encoding.UTF8, options.MediaType) - }; - message.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/graphql-response+json")); - message.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); +#pragma warning disable IDE0005 +// see https://learn.microsoft.com/en-us/dotnet/core/compatibility/sdk/8.0/implicit-global-using-netfx +using System.Diagnostics.CodeAnalysis; +using System.Net.Http; +#pragma warning restore IDE0005 +using System.Net.Http.Headers; +using System.Text; +using GraphQL.Client.Abstractions; + +namespace GraphQL.Client.Http; + +public class GraphQLHttpRequest : GraphQLRequest +{ + public GraphQLHttpRequest() + { + } + + public GraphQLHttpRequest([StringSyntax("GraphQL")] string query, object? variables = null, string? operationName = null, Dictionary? extensions = null) + : base(query, variables, operationName, extensions) + { + } + + public GraphQLHttpRequest(GraphQLRequest other) + : base(other) + { + } + + /// + /// Creates a from this . + /// Used by to convert GraphQL requests when sending them as regular HTTP requests. + /// + /// the passed from + /// the passed from + /// + public virtual HttpRequestMessage ToHttpRequestMessage(GraphQLHttpClientOptions options, IGraphQLJsonSerializer serializer) + { + var message = new HttpRequestMessage(HttpMethod.Post, options.EndPoint) + { + Content = new StringContent(serializer.SerializeToString(this), Encoding.UTF8, options.MediaType) + }; + message.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/graphql-response+json")); + message.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); message.Headers.AcceptCharset.Add(new StringWithQualityHeaderValue("utf-8")); // Explicitly setting content header to avoid issues with some GrahQL servers - message.Content.Headers.ContentType = new MediaTypeHeaderValue(options.MediaType); - - if (options.DefaultUserAgentRequestHeader != null) - message.Headers.UserAgent.Add(options.DefaultUserAgentRequestHeader); - - return message; - } -} + message.Content.Headers.ContentType = new MediaTypeHeaderValue(options.MediaType); + + if (options.DefaultUserAgentRequestHeader != null) + message.Headers.UserAgent.Add(options.DefaultUserAgentRequestHeader); + + return message; + } +} diff --git a/src/GraphQL.Primitives/StringSyntaxAttribute.cs b/src/GraphQL.Primitives/StringSyntaxAttribute.cs index 8ea74c0e..76e5b5e4 100644 --- a/src/GraphQL.Primitives/StringSyntaxAttribute.cs +++ b/src/GraphQL.Primitives/StringSyntaxAttribute.cs @@ -1,5 +1,6 @@ #if !NET7_0_OR_GREATER +// ReSharper disable once CheckNamespace namespace System.Diagnostics.CodeAnalysis; /// @@ -7,11 +8,14 @@ namespace System.Diagnostics.CodeAnalysis; /// public sealed class StringSyntaxAttribute : Attribute { + // ReSharper disable once InconsistentNaming +#pragma warning disable IDE1006 public const string CompositeFormat = nameof(CompositeFormat); +#pragma warning restore IDE1006 +#pragma warning disable IDE0060 public StringSyntaxAttribute(string syntax) - { - } - + { } +#pragma warning restore IDE0060 } #endif diff --git a/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj b/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj index edf3881f..b0efc377 100644 --- a/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj +++ b/tests/GraphQL.Client.Serializer.Tests/GraphQL.Client.Serializer.Tests.csproj @@ -7,8 +7,8 @@ - - + + @@ -20,9 +20,13 @@ - - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj index f199b89a..6b76cf7d 100644 --- a/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj +++ b/tests/GraphQL.Client.Tests.Common/GraphQL.Client.Tests.Common.csproj @@ -7,7 +7,7 @@ - + diff --git a/tests/GraphQL.Client.Tests.Common/StarWars/Types/DroidType.cs b/tests/GraphQL.Client.Tests.Common/StarWars/Types/DroidType.cs index 72680c43..661737f6 100644 --- a/tests/GraphQL.Client.Tests.Common/StarWars/Types/DroidType.cs +++ b/tests/GraphQL.Client.Tests.Common/StarWars/Types/DroidType.cs @@ -15,8 +15,7 @@ public DroidType(StarWarsData data) Field>("friends").Resolve(context => data.GetFriends(context.Source)); - Connection() - .Name("friendsConnection") + Connection("friendsConnection") .Description("A list of a character's friends.") .Bidirectional() .Resolve(context => context.GetPagedResults(data, context.Source.Friends)); diff --git a/tests/GraphQL.Client.Tests.Common/StarWars/Types/HumanType.cs b/tests/GraphQL.Client.Tests.Common/StarWars/Types/HumanType.cs index a95e01d0..ed19cf5c 100644 --- a/tests/GraphQL.Client.Tests.Common/StarWars/Types/HumanType.cs +++ b/tests/GraphQL.Client.Tests.Common/StarWars/Types/HumanType.cs @@ -14,8 +14,7 @@ public HumanType(StarWarsData data) Field>("friends").Resolve(context => data.GetFriends(context.Source)); - Connection() - .Name("friendsConnection") + Connection("friendsConnection") .Description("A list of a character's friends.") .Bidirectional() .Resolve(context => context.GetPagedResults(data, context.Source.Friends)); diff --git a/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj b/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj index bb5b4616..4a3f7dc2 100644 --- a/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj +++ b/tests/GraphQL.Integration.Tests/GraphQL.Integration.Tests.csproj @@ -7,7 +7,7 @@ - + @@ -19,9 +19,13 @@ - - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/GraphQL.Primitives.Tests/GraphQL.Primitives.Tests.csproj b/tests/GraphQL.Primitives.Tests/GraphQL.Primitives.Tests.csproj index ffe7c0a6..d17dbc94 100644 --- a/tests/GraphQL.Primitives.Tests/GraphQL.Primitives.Tests.csproj +++ b/tests/GraphQL.Primitives.Tests/GraphQL.Primitives.Tests.csproj @@ -11,9 +11,13 @@ - - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj b/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj index 00d8126c..191bfd81 100644 --- a/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj +++ b/tests/GraphQL.Server.Test/GraphQL.Server.Test.csproj @@ -6,9 +6,9 @@ - - - + + + diff --git a/tests/IntegrationTestServer/IntegrationTestServer.csproj b/tests/IntegrationTestServer/IntegrationTestServer.csproj index 66d8071d..58854750 100644 --- a/tests/IntegrationTestServer/IntegrationTestServer.csproj +++ b/tests/IntegrationTestServer/IntegrationTestServer.csproj @@ -11,7 +11,7 @@ - + From fd2cf06f7f294176719d48a7ed4f062d2baf6b14 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Mon, 22 Apr 2024 11:50:31 +0200 Subject: [PATCH 453/455] Document syntax highlighting (#637) * copy code for StringSyntaxAttribute from original source * document syntax highlighting * add note on usage of GraphQLHttpClient * add note on response type * fix wording --- README.md | 23 ++++++- .../StringSyntaxAttribute.cs | 69 ++++++++++++++++--- 2 files changed, 82 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 0866ef7a..6dc83af6 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,9 @@ The Library will try to follow the following standards and documents: var graphQLClient = new GraphQLHttpClient("https://api.example.com/graphql", new NewtonsoftJsonSerializer()); ``` +> [!NOTE] +> *GraphQLHttpClient* is meant to be used as a single longlived instance per endpoint (i.e. register as singleton in a DI system), which should be reused for multiple requests. + ### Create a GraphQLRequest: #### Simple Request: ```csharp @@ -64,7 +67,10 @@ var personAndFilmsRequest = new GraphQLRequest { }; ``` -Be careful when using `byte[]` in your variables object, as most JSON serializers will treat that as binary data! If you really need to send a *list of bytes* with a `byte[]` as a source, then convert it to a `List` first, which will tell the serializer to output a list of numbers instead of a base64-encoded string. +> [!WARNING] +> Be careful when using `byte[]` in your variables object, as most JSON serializers will treat that as binary data. +> +> If you really need to send a *list of bytes* with a `byte[]` as a source, then convert it to a `List` first, which will tell the serializer to output a list of numbers instead of a base64-encoded string. ### Execute Query/Mutation: @@ -100,6 +106,11 @@ var graphQLResponse = await graphQLClient.SendQueryAsync(personAndFilmsRequest, var personName = graphQLResponse.Data.person.Name; ``` +> [!IMPORTANT] +> Note that the field in the GraphQL response which gets deserialized into the response object is the `data` field. +> +> A common mistake is to try to directly use the `PersonType` class as response type (because thats the *thing* you actually want to query), but the returned response object contains a property `person` containing a `PersonType` object (like the `ResponseType` modelled above). + ### Use Subscriptions ```csharp @@ -141,6 +152,16 @@ var subscription = subscriptionStream.Subscribe(response => subscription.Dispose(); ``` +## Syntax Highlighting for GraphQL strings in IDEs + +.NET 7.0 introduced the [StringSyntaxAttribute](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.codeanalysis.stringsyntaxattribute?view=net-8.0) to have a unified way of telling what data is expected in a given `string` or `ReadOnlySpan`. IDEs like Visual Studio and Rider can then use this to provide syntax highlighting and checking. + +From v6.0.4 on all GraphQL string parameters in this library are decorated with the `[StringSyntax("GraphQL")]` attribute. + +Currently, there is no native support for GraphQL formatting and syntax highlighting in Visual Studio, but the [GraphQLTools Extension](https://marketplace.visualstudio.com/items?itemName=codearchitects-research.GraphQLTools) provides that for you. + +For Rider, JetBrains provides a [Plugin](https://plugins.jetbrains.com/plugin/8097-graphql), too. + ## Useful Links: * [StarWars Example Server (GitHub)](https://github.com/graphql/swapi-graphql) diff --git a/src/GraphQL.Primitives/StringSyntaxAttribute.cs b/src/GraphQL.Primitives/StringSyntaxAttribute.cs index 76e5b5e4..8be2e889 100644 --- a/src/GraphQL.Primitives/StringSyntaxAttribute.cs +++ b/src/GraphQL.Primitives/StringSyntaxAttribute.cs @@ -1,21 +1,72 @@ #if !NET7_0_OR_GREATER +// ReSharper disable InconsistentNaming // ReSharper disable once CheckNamespace namespace System.Diagnostics.CodeAnalysis; -/// -/// Stub -/// +/// Stub version of the StringSyntaxAttribute, which was introduced in .NET 7 public sealed class StringSyntaxAttribute : Attribute { - // ReSharper disable once InconsistentNaming + /// Initializes the with the identifier of the syntax used. + /// The syntax identifier. + public StringSyntaxAttribute(string syntax) + { + Syntax = syntax; + Arguments = Array.Empty(); + } + + /// Initializes the with the identifier of the syntax used. + /// The syntax identifier. + /// Optional arguments associated with the specific syntax employed. + public StringSyntaxAttribute(string syntax, params object?[] arguments) + { + Syntax = syntax; + Arguments = arguments; + } + + /// Gets the identifier of the syntax used. + public string Syntax { get; } + + /// Optional arguments associated with the specific syntax employed. + public object?[] Arguments { get; } + + /// The syntax identifier for strings containing composite formats for string formatting. #pragma warning disable IDE1006 public const string CompositeFormat = nameof(CompositeFormat); -#pragma warning restore IDE1006 -#pragma warning disable IDE0060 - public StringSyntaxAttribute(string syntax) - { } -#pragma warning restore IDE0060 + /// The syntax identifier for strings containing date format specifiers. + public const string DateOnlyFormat = nameof(DateOnlyFormat); + + /// The syntax identifier for strings containing date and time format specifiers. + public const string DateTimeFormat = nameof(DateTimeFormat); + + /// The syntax identifier for strings containing format specifiers. + public const string EnumFormat = nameof(EnumFormat); + + /// The syntax identifier for strings containing format specifiers. + public const string GuidFormat = nameof(GuidFormat); + + /// The syntax identifier for strings containing JavaScript Object Notation (JSON). + public const string Json = nameof(Json); + + /// The syntax identifier for strings containing numeric format specifiers. + public const string NumericFormat = nameof(NumericFormat); + + /// The syntax identifier for strings containing regular expressions. + public const string Regex = nameof(Regex); + + /// The syntax identifier for strings containing time format specifiers. + public const string TimeOnlyFormat = nameof(TimeOnlyFormat); + + /// The syntax identifier for strings containing format specifiers. + public const string TimeSpanFormat = nameof(TimeSpanFormat); + + /// The syntax identifier for strings containing URIs. + public const string Uri = nameof(Uri); + + /// The syntax identifier for strings containing XML. + public const string Xml = nameof(Xml); +#pragma warning restore IDE1006 } + #endif From dbd9c20cf8bcb0625aebe5dfb9317309c6755b01 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Mon, 22 Apr 2024 16:29:05 +0200 Subject: [PATCH 454/455] Add GraphQLQuery record type for reusable query declarations with syntax highlighting (#638) * add GraphQLQuery record type for reusable query declarations * enable GraphQLQuery record from .NET 6.0 upwards * document GraphQLQuery type * optimize linebreaks in Readme * fix code formatting in readme --- README.md | 53 ++++++++++++++----- .../GraphQL.Client.Abstractions.csproj | 2 +- .../GraphQLClientExtensions.cs | 21 +++++++- src/GraphQL.Client/GraphQL.Client.csproj | 2 +- src/GraphQL.Client/GraphQLHttpRequest.cs | 7 +++ .../GraphQLSubscriptionException.cs | 11 ++-- .../GraphQLWebsocketConnectionException.cs | 11 ++-- .../GraphQL.Primitives.csproj | 2 +- src/GraphQL.Primitives/GraphQLQuery.cs | 15 ++++++ src/GraphQL.Primitives/GraphQLRequest.cs | 8 +++ .../QueryAndMutationTests/Base.cs | 16 +++--- 11 files changed, 114 insertions(+), 34 deletions(-) create mode 100644 src/GraphQL.Primitives/GraphQLQuery.cs diff --git a/README.md b/README.md index 6dc83af6..fe859ced 100644 --- a/README.md +++ b/README.md @@ -14,34 +14,38 @@ Provides the following packages: | GraphQL.Client.Serializer.SystemTextJson | [![Nuget](https://img.shields.io/nuget/dt/GraphQL.Client.Serializer.SystemTextJson)](https://www.nuget.org/packages/GraphQL.Client.Serializer.SystemTextJson) | [![Nuget](https://img.shields.io/nuget/vpre/GraphQL.Client.Serializer.SystemTextJson)](https://www.nuget.org/packages/GraphQL.Client.Serializer.SystemTextJson) | | GraphQL.Primitives | [![Nuget](https://img.shields.io/nuget/dt/GraphQL.Primitives)](https://www.nuget.org/packages/GraphQL.Primitives/) | [![Nuget](https://img.shields.io/nuget/vpre/GraphQL.Primitives)](https://www.nuget.org/packages/GraphQL.Primitives) | -## Specification: +## Specification The Library will try to follow the following standards and documents: * [GraphQL Specification](https://spec.graphql.org/June2018/) * [GraphQL HomePage](https://graphql.org/learn) -## Usage: +## Usage ### Create a GraphQLHttpClient ```csharp -// To use NewtonsoftJsonSerializer, add a reference to NuGet package GraphQL.Client.Serializer.Newtonsoft -var graphQLClient = new GraphQLHttpClient("https://api.example.com/graphql", new NewtonsoftJsonSerializer()); +// To use NewtonsoftJsonSerializer, add a reference to +// NuGet package GraphQL.Client.Serializer.Newtonsoft +var graphQLClient = new GraphQLHttpClient( + "https://api.example.com/graphql", + new NewtonsoftJsonSerializer()); ``` > [!NOTE] -> *GraphQLHttpClient* is meant to be used as a single longlived instance per endpoint (i.e. register as singleton in a DI system), which should be reused for multiple requests. +> *GraphQLHttpClient* is meant to be used as a single long-lived instance per endpoint (i.e. register as singleton in a DI system), which should be reused for multiple requests. ### Create a GraphQLRequest: #### Simple Request: ```csharp var heroRequest = new GraphQLRequest { - Query = @" + Query = """ { hero { name } - }" + } + """ }; ``` @@ -49,7 +53,7 @@ var heroRequest = new GraphQLRequest { ```csharp var personAndFilmsRequest = new GraphQLRequest { - Query =@" + Query =""" query PersonAndFilms($id: ID) { person(id: $id) { name @@ -59,7 +63,8 @@ var personAndFilmsRequest = new GraphQLRequest { } } } - }", + } + """, OperationName = "PersonAndFilms", Variables = new { id = "cGVvcGxlOjE=" @@ -72,7 +77,7 @@ var personAndFilmsRequest = new GraphQLRequest { > > If you really need to send a *list of bytes* with a `byte[]` as a source, then convert it to a `List` first, which will tell the serializer to output a list of numbers instead of a base64-encoded string. -### Execute Query/Mutation: +### Execute Query/Mutation ```csharp public class ResponseType @@ -102,7 +107,9 @@ var personName = graphQLResponse.Data.Person.Name; Using the extension method for anonymously typed responses (namespace `GraphQL.Client.Abstractions`) you could achieve the same result with the following code: ```csharp -var graphQLResponse = await graphQLClient.SendQueryAsync(personAndFilmsRequest, () => new { person = new PersonType()} ); +var graphQLResponse = await graphQLClient.SendQueryAsync( + personAndFilmsRequest, + () => new { person = new PersonType()}); var personName = graphQLResponse.Data.person.Name; ``` @@ -162,7 +169,29 @@ Currently, there is no native support for GraphQL formatting and syntax highligh For Rider, JetBrains provides a [Plugin](https://plugins.jetbrains.com/plugin/8097-graphql), too. -## Useful Links: +To leverage syntax highlighting in variable declarations, the `GraphQLQuery` value record type is provided: + +```csharp +GraphQLQuery query = new(""" + query PersonAndFilms($id: ID) { + person(id: $id) { + name + filmConnection { + films { + title + } + } + } + } + """); + +var graphQLResponse = await graphQLClient.SendQueryAsync( + query, + "PersonAndFilms", + new { id = "cGVvcGxlOjE=" }); +``` + +## Useful Links * [StarWars Example Server (GitHub)](https://github.com/graphql/swapi-graphql) * [StarWars Example Server (EndPoint)](https://swapi.apis.guru/) diff --git a/src/GraphQL.Client.Abstractions/GraphQL.Client.Abstractions.csproj b/src/GraphQL.Client.Abstractions/GraphQL.Client.Abstractions.csproj index a18177ca..4fa8f98f 100644 --- a/src/GraphQL.Client.Abstractions/GraphQL.Client.Abstractions.csproj +++ b/src/GraphQL.Client.Abstractions/GraphQL.Client.Abstractions.csproj @@ -2,7 +2,7 @@ Abstractions for GraphQL.Client - netstandard2.0;net7.0;net8.0 + netstandard2.0;net6.0;net7.0;net8.0 diff --git a/src/GraphQL.Client.Abstractions/GraphQLClientExtensions.cs b/src/GraphQL.Client.Abstractions/GraphQLClientExtensions.cs index cce6db1a..92b03980 100644 --- a/src/GraphQL.Client.Abstractions/GraphQLClientExtensions.cs +++ b/src/GraphQL.Client.Abstractions/GraphQLClientExtensions.cs @@ -6,22 +6,39 @@ public static class GraphQLClientExtensions { public static Task> SendQueryAsync(this IGraphQLClient client, [StringSyntax("GraphQL")] string query, object? variables = null, - string? operationName = null, Func defineResponseType = null, CancellationToken cancellationToken = default) + string? operationName = null, Func? defineResponseType = null, CancellationToken cancellationToken = default) { _ = defineResponseType; return client.SendQueryAsync(new GraphQLRequest(query, variables, operationName), cancellationToken: cancellationToken); } +#if NET6_0_OR_GREATER + public static Task> SendQueryAsync(this IGraphQLClient client, + GraphQLQuery query, object? variables = null, + string? operationName = null, Func? defineResponseType = null, + CancellationToken cancellationToken = default) + => SendQueryAsync(client, query.Text, variables, operationName, defineResponseType, + cancellationToken); +#endif + public static Task> SendMutationAsync(this IGraphQLClient client, [StringSyntax("GraphQL")] string query, object? variables = null, - string? operationName = null, Func defineResponseType = null, CancellationToken cancellationToken = default) + string? operationName = null, Func? defineResponseType = null, CancellationToken cancellationToken = default) { _ = defineResponseType; return client.SendMutationAsync(new GraphQLRequest(query, variables, operationName), cancellationToken: cancellationToken); } +#if NET6_0_OR_GREATER + public static Task> SendMutationAsync(this IGraphQLClient client, + GraphQLQuery query, object? variables = null, string? operationName = null, Func? defineResponseType = null, + CancellationToken cancellationToken = default) + => SendMutationAsync(client, query.Text, variables, operationName, defineResponseType, + cancellationToken); +#endif + public static Task> SendQueryAsync(this IGraphQLClient client, GraphQLRequest request, Func defineResponseType, CancellationToken cancellationToken = default) { diff --git a/src/GraphQL.Client/GraphQL.Client.csproj b/src/GraphQL.Client/GraphQL.Client.csproj index 241e3bcc..19a2bb7d 100644 --- a/src/GraphQL.Client/GraphQL.Client.csproj +++ b/src/GraphQL.Client/GraphQL.Client.csproj @@ -1,7 +1,7 @@ - netstandard2.0;net461 + netstandard2.0;net461;net6.0;net7.0;net8.0 GraphQL.Client.Http diff --git a/src/GraphQL.Client/GraphQLHttpRequest.cs b/src/GraphQL.Client/GraphQLHttpRequest.cs index b89d1814..3882195a 100644 --- a/src/GraphQL.Client/GraphQLHttpRequest.cs +++ b/src/GraphQL.Client/GraphQLHttpRequest.cs @@ -20,6 +20,13 @@ public GraphQLHttpRequest([StringSyntax("GraphQL")] string query, object? variab { } +#if NET6_0_OR_GREATER + public GraphQLHttpRequest(GraphQLQuery query, object? variables = null, string? operationName = null, Dictionary? extensions = null) + : base(query, variables, operationName, extensions) + { + } +#endif + public GraphQLHttpRequest(GraphQLRequest other) : base(other) { diff --git a/src/GraphQL.Client/GraphQLSubscriptionException.cs b/src/GraphQL.Client/GraphQLSubscriptionException.cs index ccaab426..21dd8982 100644 --- a/src/GraphQL.Client/GraphQLSubscriptionException.cs +++ b/src/GraphQL.Client/GraphQLSubscriptionException.cs @@ -1,17 +1,12 @@ +#if !NET8_0_OR_GREATER using System.Runtime.Serialization; +#endif namespace GraphQL.Client.Http; [Serializable] public class GraphQLSubscriptionException : Exception { - // - // For guidelines regarding the creation of new exception types, see - // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp - // and - // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp - // - public GraphQLSubscriptionException() { } @@ -20,9 +15,11 @@ public GraphQLSubscriptionException(object error) : base(error.ToString()) { } +#if !NET8_0_OR_GREATER protected GraphQLSubscriptionException( SerializationInfo info, StreamingContext context) : base(info, context) { } +#endif } diff --git a/src/GraphQL.Client/Websocket/GraphQLWebsocketConnectionException.cs b/src/GraphQL.Client/Websocket/GraphQLWebsocketConnectionException.cs index f9811b98..aedebb90 100644 --- a/src/GraphQL.Client/Websocket/GraphQLWebsocketConnectionException.cs +++ b/src/GraphQL.Client/Websocket/GraphQLWebsocketConnectionException.cs @@ -1,4 +1,6 @@ +#if !NET8_0_OR_GREATER using System.Runtime.Serialization; +#endif namespace GraphQL.Client.Http.Websocket; @@ -9,15 +11,18 @@ public GraphQLWebsocketConnectionException() { } - protected GraphQLWebsocketConnectionException(SerializationInfo info, StreamingContext context) : base(info, context) + public GraphQLWebsocketConnectionException(string message) : base(message) { } - public GraphQLWebsocketConnectionException(string message) : base(message) + public GraphQLWebsocketConnectionException(string message, Exception innerException) : base(message, innerException) { } - public GraphQLWebsocketConnectionException(string message, Exception innerException) : base(message, innerException) +#if !NET8_0_OR_GREATER + protected GraphQLWebsocketConnectionException(SerializationInfo info, StreamingContext context) : base(info, context) { } +#endif + } diff --git a/src/GraphQL.Primitives/GraphQL.Primitives.csproj b/src/GraphQL.Primitives/GraphQL.Primitives.csproj index b49b7360..44f6e6fe 100644 --- a/src/GraphQL.Primitives/GraphQL.Primitives.csproj +++ b/src/GraphQL.Primitives/GraphQL.Primitives.csproj @@ -3,7 +3,7 @@ GraphQL basic types GraphQL - netstandard2.0;net7.0;net8.0 + netstandard2.0;net6.0;net7.0;net8.0 diff --git a/src/GraphQL.Primitives/GraphQLQuery.cs b/src/GraphQL.Primitives/GraphQLQuery.cs new file mode 100644 index 00000000..b4ccf635 --- /dev/null +++ b/src/GraphQL.Primitives/GraphQLQuery.cs @@ -0,0 +1,15 @@ +#if NET6_0_OR_GREATER +using System.Diagnostics.CodeAnalysis; + +namespace GraphQL; + +/// +/// Value record for a GraphQL query string +/// +/// the actual query string +public readonly record struct GraphQLQuery([StringSyntax("GraphQL")] string Text) +{ + public static implicit operator string(GraphQLQuery query) + => query.Text; +}; +#endif diff --git a/src/GraphQL.Primitives/GraphQLRequest.cs b/src/GraphQL.Primitives/GraphQLRequest.cs index 7aecb656..c208a90f 100644 --- a/src/GraphQL.Primitives/GraphQLRequest.cs +++ b/src/GraphQL.Primitives/GraphQLRequest.cs @@ -59,6 +59,14 @@ public GraphQLRequest([StringSyntax("GraphQL")] string query, object? variables Extensions = extensions; } +#if NET6_0_OR_GREATER + public GraphQLRequest(GraphQLQuery query, object? variables = null, string? operationName = null, + Dictionary? extensions = null) + : this(query.Text, variables, operationName, extensions) + { + } +#endif + public GraphQLRequest(GraphQLRequest other) : base(other) { } /// diff --git a/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs b/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs index cb25adc6..e20fdd39 100644 --- a/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs +++ b/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs @@ -82,13 +82,15 @@ public async void QueryWithDynamicReturnTypeTheory(int id, string name) [ClassData(typeof(StarWarsHumans))] public async void QueryWitVarsTheory(int id, string name) { - var graphQLRequest = new GraphQLRequest(@" - query Human($id: String!){ - human(id: $id) { - name - } - }", - new { id = id.ToString() }); + var query = new GraphQLQuery(""" + query Human($id: String!){ + human(id: $id) { + name + } + } + """); + + var graphQLRequest = new GraphQLRequest(query, new { id = id.ToString() }); var response = await StarWarsClient.SendQueryAsync(graphQLRequest, () => new { Human = new { Name = string.Empty } }); From 6236c9b2c6f3568f96a9f8e12aa6fb367321c068 Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Tue, 21 May 2024 09:34:59 +0300 Subject: [PATCH 455/455] Add APQ support (#555) * Add APQ support * changes * rem * note * progress * progress * fix variable name * move APQ code to SendQueryAsync method to allow usage over websocket, too * make the APQDisabledForSession flag public (helps for testing) * create a test that uses the APQ feature * test APQ with websocket transport * move code for generation of the APQ extension into GraphQLRequest * fix naming * replace system.memory reference with narrower system.buffers reference * Update src/GraphQL.Primitives/GraphQLRequest.cs Co-authored-by: Shane Krueger * Update src/GraphQL.Primitives/GraphQLRequest.cs Co-authored-by: Shane Krueger * document APQ feature +semver: feature * optimize docs --------- Co-authored-by: Alexander Rose Co-authored-by: Alexander Rose Co-authored-by: Shane Krueger --- GraphQL.Client.sln.DotSettings | 1 + README.md | 33 +++++- .../GraphQLClientExtensions.cs | 4 - src/GraphQL.Client/GraphQLHttpClient.cs | 54 ++++++++- .../GraphQLHttpClientOptions.cs | 19 ++- src/GraphQL.Client/GraphQLHttpRequest.cs | 3 - src/GraphQL.Client/GraphQLHttpResponse.cs | 9 +- .../GraphQL.Primitives.csproj | 3 + src/GraphQL.Primitives/GraphQLQuery.cs | 33 ++++-- src/GraphQL.Primitives/GraphQLRequest.cs | 33 +++++- src/GraphQL.Primitives/Hash.cs | 44 +++++++ .../APQ/AutomaticPersistentQueriesTest.cs | 110 ++++++++++++++++++ tests/IntegrationTestServer/Startup.cs | 1 + 13 files changed, 315 insertions(+), 32 deletions(-) create mode 100644 src/GraphQL.Primitives/Hash.cs create mode 100644 tests/GraphQL.Integration.Tests/APQ/AutomaticPersistentQueriesTest.cs diff --git a/GraphQL.Client.sln.DotSettings b/GraphQL.Client.sln.DotSettings index 9e5ec22f..230ed27f 100644 --- a/GraphQL.Client.sln.DotSettings +++ b/GraphQL.Client.sln.DotSettings @@ -1,2 +1,3 @@  + APQ QL \ No newline at end of file diff --git a/README.md b/README.md index fe859ced..52ed68d7 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,9 @@ The Library will try to follow the following standards and documents: ## Usage +The intended use of `GraphQLHttpClient` is to keep one instance alive per endpoint (obvious in case you're +operating full websocket, but also true for regular requests) and is built with thread-safety in mind. + ### Create a GraphQLHttpClient ```csharp @@ -159,17 +162,22 @@ var subscription = subscriptionStream.Subscribe(response => subscription.Dispose(); ``` -## Syntax Highlighting for GraphQL strings in IDEs +### Automatic persisted queries (APQ) -.NET 7.0 introduced the [StringSyntaxAttribute](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.codeanalysis.stringsyntaxattribute?view=net-8.0) to have a unified way of telling what data is expected in a given `string` or `ReadOnlySpan`. IDEs like Visual Studio and Rider can then use this to provide syntax highlighting and checking. +[Automatic persisted queries (APQ)](https://www.apollographql.com/docs/apollo-server/performance/apq/) are supported since client version 6.1.0. -From v6.0.4 on all GraphQL string parameters in this library are decorated with the `[StringSyntax("GraphQL")]` attribute. +APQ can be enabled by configuring `GraphQLHttpClientOptions.EnableAutomaticPersistedQueries` to resolve to `true`. -Currently, there is no native support for GraphQL formatting and syntax highlighting in Visual Studio, but the [GraphQLTools Extension](https://marketplace.visualstudio.com/items?itemName=codearchitects-research.GraphQLTools) provides that for you. +By default, the client will automatically disable APQ for the current session if the server responds with a `PersistedQueryNotSupported` error or a 400 or 600 HTTP status code. +This can be customized by configuring `GraphQLHttpClientOptions.DisableAPQ`. -For Rider, JetBrains provides a [Plugin](https://plugins.jetbrains.com/plugin/8097-graphql), too. +To re-enable APQ after it has been automatically disabled, `GraphQLHttpClient` needs to be disposed an recreated. -To leverage syntax highlighting in variable declarations, the `GraphQLQuery` value record type is provided: +APQ works by first sending a hash of the query string to the server, and only sending the full query string if the server has not yet cached a query with a matching hash. +With queries supplied as a string parameter to `GraphQLRequest`, the hash gets computed each time the request is sent. + +When you want to reuse a query string (propably to leverage APQ :wink:), declare the query using the `GraphQLQuery` class. This way, the hash gets computed once on construction +of the `GraphQLQuery` object and handed down to each `GraphQLRequest` using the query. ```csharp GraphQLQuery query = new(""" @@ -191,6 +199,19 @@ var graphQLResponse = await graphQLClient.SendQueryAsync( new { id = "cGVvcGxlOjE=" }); ``` +### Syntax Highlighting for GraphQL strings in IDEs + +.NET 7.0 introduced the [StringSyntaxAttribute](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.codeanalysis.stringsyntaxattribute?view=net-8.0) to have a unified way of telling what data is expected in a given `string` or `ReadOnlySpan`. IDEs like Visual Studio and Rider can then use this to provide syntax highlighting and checking. + +From v6.0.4 on all GraphQL string parameters in this library are decorated with the `[StringSyntax("GraphQL")]` attribute. + +Currently, there is no native support for GraphQL formatting and syntax highlighting in Visual Studio, but the [GraphQLTools Extension](https://marketplace.visualstudio.com/items?itemName=codearchitects-research.GraphQLTools) provides that for you. + +For Rider, JetBrains provides a [Plugin](https://plugins.jetbrains.com/plugin/8097-graphql), too. + +To leverage syntax highlighting in variable declarations, use the `GraphQLQuery` class. + + ## Useful Links * [StarWars Example Server (GitHub)](https://github.com/graphql/swapi-graphql) diff --git a/src/GraphQL.Client.Abstractions/GraphQLClientExtensions.cs b/src/GraphQL.Client.Abstractions/GraphQLClientExtensions.cs index 92b03980..c2e4bb7c 100644 --- a/src/GraphQL.Client.Abstractions/GraphQLClientExtensions.cs +++ b/src/GraphQL.Client.Abstractions/GraphQLClientExtensions.cs @@ -13,14 +13,12 @@ public static Task> SendQueryAsync(this IG cancellationToken: cancellationToken); } -#if NET6_0_OR_GREATER public static Task> SendQueryAsync(this IGraphQLClient client, GraphQLQuery query, object? variables = null, string? operationName = null, Func? defineResponseType = null, CancellationToken cancellationToken = default) => SendQueryAsync(client, query.Text, variables, operationName, defineResponseType, cancellationToken); -#endif public static Task> SendMutationAsync(this IGraphQLClient client, [StringSyntax("GraphQL")] string query, object? variables = null, @@ -31,13 +29,11 @@ public static Task> SendMutationAsync(this cancellationToken: cancellationToken); } -#if NET6_0_OR_GREATER public static Task> SendMutationAsync(this IGraphQLClient client, GraphQLQuery query, object? variables = null, string? operationName = null, Func? defineResponseType = null, CancellationToken cancellationToken = default) => SendMutationAsync(client, query.Text, variables, operationName, defineResponseType, cancellationToken); -#endif public static Task> SendQueryAsync(this IGraphQLClient client, GraphQLRequest request, Func defineResponseType, CancellationToken cancellationToken = default) diff --git a/src/GraphQL.Client/GraphQLHttpClient.cs b/src/GraphQL.Client/GraphQLHttpClient.cs index 10076b8a..5f54d21b 100644 --- a/src/GraphQL.Client/GraphQLHttpClient.cs +++ b/src/GraphQL.Client/GraphQLHttpClient.cs @@ -17,7 +17,6 @@ public class GraphQLHttpClient : IGraphQLWebSocketClient, IDisposable private readonly CancellationTokenSource _cancellationTokenSource = new(); private readonly bool _disposeHttpClient = false; - /// /// the json serializer /// @@ -33,6 +32,12 @@ public class GraphQLHttpClient : IGraphQLWebSocketClient, IDisposable /// public GraphQLHttpClientOptions Options { get; } + /// + /// This flag is set to when an error has occurred on an APQ and + /// has returned . To reset this, the instance of has to be disposed and a new one must be created. + /// + public bool APQDisabledForSession { get; private set; } + /// public IObservable WebSocketReceiveErrors => GraphQlHttpWebSocket.ReceiveErrors; @@ -84,12 +89,49 @@ public GraphQLHttpClient(string endPoint, IGraphQLWebsocketJsonSerializer serial #region IGraphQLClient + private const int APQ_SUPPORTED_VERSION = 1; + /// public async Task> SendQueryAsync(GraphQLRequest request, CancellationToken cancellationToken = default) { - return Options.UseWebSocketForQueriesAndMutations || Options.WebSocketEndPoint is not null && Options.EndPoint is null || Options.EndPoint.HasWebSocketScheme() - ? await GraphQlHttpWebSocket.SendRequestAsync(request, cancellationToken).ConfigureAwait(false) - : await SendHttpRequestAsync(request, cancellationToken).ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + + string? savedQuery = null; + bool useAPQ = false; + + if (request.Query != null && !APQDisabledForSession && Options.EnableAutomaticPersistedQueries(request)) + { + // https://www.apollographql.com/docs/react/api/link/persisted-queries/ + useAPQ = true; + request.GeneratePersistedQueryExtension(); + savedQuery = request.Query; + request.Query = null; + } + + var response = await SendQueryInternalAsync(request, cancellationToken); + + if (useAPQ) + { + if (response.Errors?.Any(error => string.Equals(error.Message, "PersistedQueryNotFound", StringComparison.CurrentCultureIgnoreCase)) == true) + { + // GraphQL server supports APQ! + + // Alas, for the first time we did not guess and in vain removed Query, so we return Query and + // send request again. This is one-time "cache miss", not so scary. + request.Query = savedQuery; + return await SendQueryInternalAsync(request, cancellationToken); + } + else + { + // GraphQL server either supports APQ of some other version, or does not support it at all. + // Send a request for the second time. This is better than returning an error. Let the client work with APQ disabled. + APQDisabledForSession = Options.DisableAPQ(response); + request.Query = savedQuery; + return await SendQueryInternalAsync(request, cancellationToken); + } + } + + return response; } /// @@ -123,6 +165,10 @@ public IObservable> CreateSubscriptionStream GraphQlHttpWebSocket.SendPongAsync(payload); #region Private Methods + private async Task> SendQueryInternalAsync(GraphQLRequest request, CancellationToken cancellationToken = default) => + Options.UseWebSocketForQueriesAndMutations || Options.WebSocketEndPoint is not null && Options.EndPoint is null || Options.EndPoint.HasWebSocketScheme() + ? await GraphQlHttpWebSocket.SendRequestAsync(request, cancellationToken).ConfigureAwait(false) + : await SendHttpRequestAsync(request, cancellationToken).ConfigureAwait(false); private async Task> SendHttpRequestAsync(GraphQLRequest request, CancellationToken cancellationToken = default) { diff --git a/src/GraphQL.Client/GraphQLHttpClientOptions.cs b/src/GraphQL.Client/GraphQLHttpClientOptions.cs index 6c3b30d1..eda0d024 100644 --- a/src/GraphQL.Client/GraphQLHttpClientOptions.cs +++ b/src/GraphQL.Client/GraphQLHttpClientOptions.cs @@ -25,7 +25,7 @@ public class GraphQLHttpClientOptions public Uri? WebSocketEndPoint { get; set; } /// - /// The GraphQL websocket protocol to be used. Defaults to the older "graphql-ws" protocol to not break old code. + /// The GraphQL websocket protocol to be used. Defaults to the older "graphql-ws" protocol to not break old code. /// public string? WebSocketProtocol { get; set; } = WebSocketProtocols.AUTO_NEGOTIATE; @@ -99,4 +99,21 @@ public static bool DefaultIsValidResponseToDeserialize(HttpResponseMessage r) /// public ProductInfoHeaderValue? DefaultUserAgentRequestHeader { get; set; } = new ProductInfoHeaderValue(typeof(GraphQLHttpClient).Assembly.GetName().Name, typeof(GraphQLHttpClient).Assembly.GetName().Version.ToString()); + + /// + /// Delegate permitting use of Automatic Persisted Queries (APQ). + /// By default, returns for all requests. Note that GraphQL server should support APQ. Otherwise, the client disables APQ completely + /// after an unsuccessful attempt to send an APQ request and then send only regular requests. + /// + public Func EnableAutomaticPersistedQueries { get; set; } = _ => false; + + /// + /// A delegate which takes an and returns a boolean to disable any future persisted queries for that session. + /// This defaults to disabling on PersistedQueryNotSupported or a 400 or 500 HTTP error. + /// + public Func DisableAPQ { get; set; } = response => + { + return response.Errors?.Any(error => string.Equals(error.Message, "PersistedQueryNotSupported", StringComparison.CurrentCultureIgnoreCase)) == true + || response is IGraphQLHttpResponse httpResponse && (int)httpResponse.StatusCode >= 400 && (int)httpResponse.StatusCode < 600; + }; } diff --git a/src/GraphQL.Client/GraphQLHttpRequest.cs b/src/GraphQL.Client/GraphQLHttpRequest.cs index 3882195a..67f37892 100644 --- a/src/GraphQL.Client/GraphQLHttpRequest.cs +++ b/src/GraphQL.Client/GraphQLHttpRequest.cs @@ -19,13 +19,10 @@ public GraphQLHttpRequest([StringSyntax("GraphQL")] string query, object? variab : base(query, variables, operationName, extensions) { } - -#if NET6_0_OR_GREATER public GraphQLHttpRequest(GraphQLQuery query, object? variables = null, string? operationName = null, Dictionary? extensions = null) : base(query, variables, operationName, extensions) { } -#endif public GraphQLHttpRequest(GraphQLRequest other) : base(other) diff --git a/src/GraphQL.Client/GraphQLHttpResponse.cs b/src/GraphQL.Client/GraphQLHttpResponse.cs index cc676851..8b4f53ba 100644 --- a/src/GraphQL.Client/GraphQLHttpResponse.cs +++ b/src/GraphQL.Client/GraphQLHttpResponse.cs @@ -3,7 +3,7 @@ namespace GraphQL.Client.Http; -public class GraphQLHttpResponse : GraphQLResponse +public class GraphQLHttpResponse : GraphQLResponse, IGraphQLHttpResponse { public GraphQLHttpResponse(GraphQLResponse response, HttpResponseHeaders responseHeaders, HttpStatusCode statusCode) { @@ -19,6 +19,13 @@ public GraphQLHttpResponse(GraphQLResponse response, HttpResponseHeaders resp public HttpStatusCode StatusCode { get; set; } } +public interface IGraphQLHttpResponse : IGraphQLResponse +{ + HttpResponseHeaders ResponseHeaders { get; set; } + + HttpStatusCode StatusCode { get; set; } +} + public static class GraphQLResponseExtensions { public static GraphQLHttpResponse ToGraphQLHttpResponse(this GraphQLResponse response, HttpResponseHeaders responseHeaders, HttpStatusCode statusCode) => new(response, responseHeaders, statusCode); diff --git a/src/GraphQL.Primitives/GraphQL.Primitives.csproj b/src/GraphQL.Primitives/GraphQL.Primitives.csproj index 44f6e6fe..9cbacbe6 100644 --- a/src/GraphQL.Primitives/GraphQL.Primitives.csproj +++ b/src/GraphQL.Primitives/GraphQL.Primitives.csproj @@ -6,4 +6,7 @@ netstandard2.0;net6.0;net7.0;net8.0 + + + diff --git a/src/GraphQL.Primitives/GraphQLQuery.cs b/src/GraphQL.Primitives/GraphQLQuery.cs index b4ccf635..df6eded8 100644 --- a/src/GraphQL.Primitives/GraphQLQuery.cs +++ b/src/GraphQL.Primitives/GraphQLQuery.cs @@ -1,15 +1,34 @@ -#if NET6_0_OR_GREATER using System.Diagnostics.CodeAnalysis; - namespace GraphQL; /// -/// Value record for a GraphQL query string +/// Value object representing a GraphQL query string and storing the corresponding APQ hash.
+/// Use this to hold query strings you want to use more than once. ///
-/// the actual query string -public readonly record struct GraphQLQuery([StringSyntax("GraphQL")] string Text) +public class GraphQLQuery : IEquatable { + /// + /// The actual query string + /// + public string Text { get; } + + /// + /// The SHA256 hash used for the automatic persisted queries feature (APQ) + /// + public string Sha256Hash { get; } + + public GraphQLQuery([StringSyntax("GraphQL")] string text) + { + Text = text; + Sha256Hash = Hash.Compute(Text); + } + public static implicit operator string(GraphQLQuery query) => query.Text; -}; -#endif + + public bool Equals(GraphQLQuery other) => Sha256Hash == other.Sha256Hash; + + public override bool Equals(object? obj) => obj is GraphQLQuery other && Equals(other); + + public override int GetHashCode() => Sha256Hash.GetHashCode(); +} diff --git a/src/GraphQL.Primitives/GraphQLRequest.cs b/src/GraphQL.Primitives/GraphQLRequest.cs index c208a90f..2d3e13ce 100644 --- a/src/GraphQL.Primitives/GraphQLRequest.cs +++ b/src/GraphQL.Primitives/GraphQLRequest.cs @@ -11,19 +11,28 @@ public class GraphQLRequest : Dictionary, IEquatable - /// The Query + /// The query string ///
[StringSyntax("GraphQL")] - public string Query + public string? Query { get => TryGetValue(QUERY_KEY, out object value) ? (string)value : null; - set => this[QUERY_KEY] = value; + set + { + this[QUERY_KEY] = value; + // if the query string gets overwritten, reset the hash value + _sha265Hash = null; + } } /// - /// The name of the Operation + /// The operation to execute /// public string? OperationName { @@ -59,16 +68,28 @@ public GraphQLRequest([StringSyntax("GraphQL")] string query, object? variables Extensions = extensions; } -#if NET6_0_OR_GREATER public GraphQLRequest(GraphQLQuery query, object? variables = null, string? operationName = null, Dictionary? extensions = null) : this(query.Text, variables, operationName, extensions) { + _sha265Hash = query.Sha256Hash; } -#endif public GraphQLRequest(GraphQLRequest other) : base(other) { } + public void GeneratePersistedQueryExtension() + { + if (Query is null) + throw new InvalidOperationException($"{nameof(Query)} is null"); + + Extensions ??= new(); + Extensions[EXTENSIONS_PERSISTED_QUERY_KEY] = new Dictionary + { + ["version"] = APQ_SUPPORTED_VERSION, + ["sha256Hash"] = _sha265Hash ??= Hash.Compute(Query), + }; + } + /// /// Returns a value that indicates whether this instance is equal to a specified object /// diff --git a/src/GraphQL.Primitives/Hash.cs b/src/GraphQL.Primitives/Hash.cs new file mode 100644 index 00000000..e360dd65 --- /dev/null +++ b/src/GraphQL.Primitives/Hash.cs @@ -0,0 +1,44 @@ +using System.Buffers; +using System.Diagnostics; +using System.Security.Cryptography; +using System.Text; + +namespace GraphQL; + +internal static class Hash +{ + private static SHA256? _sha256; + + internal static string Compute(string query) + { + int expected = Encoding.UTF8.GetByteCount(query); + byte[]? inputBytes = ArrayPool.Shared.Rent(expected); + int written = Encoding.UTF8.GetBytes(query, 0, query.Length, inputBytes, 0); + Debug.Assert(written == expected, (string)$"Encoding.UTF8.GetBytes returned unexpected bytes: {written} instead of {expected}"); + + var shaShared = Interlocked.Exchange(ref _sha256, null) ?? SHA256.Create(); + +#if NET5_0_OR_GREATER + Span bytes = stackalloc byte[32]; + if (!shaShared.TryComputeHash(inputBytes.AsSpan().Slice(0, written), bytes, out int bytesWritten)) // bytesWritten ignored since it is always 32 + throw new InvalidOperationException("Too small buffer for hash"); +#else + byte[] bytes = shaShared.ComputeHash(inputBytes, 0, written); +#endif + + ArrayPool.Shared.Return(inputBytes); + Interlocked.CompareExchange(ref _sha256, shaShared, null); + +#if NET5_0_OR_GREATER + return Convert.ToHexString(bytes); +#else + var builder = new StringBuilder(bytes.Length * 2); + foreach (byte item in bytes) + { + builder.Append(item.ToString("x2")); + } + + return builder.ToString(); +#endif + } +} diff --git a/tests/GraphQL.Integration.Tests/APQ/AutomaticPersistentQueriesTest.cs b/tests/GraphQL.Integration.Tests/APQ/AutomaticPersistentQueriesTest.cs new file mode 100644 index 00000000..0c71a4ee --- /dev/null +++ b/tests/GraphQL.Integration.Tests/APQ/AutomaticPersistentQueriesTest.cs @@ -0,0 +1,110 @@ +using System.Diagnostics.CodeAnalysis; +using FluentAssertions; +using GraphQL.Client.Abstractions; +using GraphQL.Client.Http; +using GraphQL.Client.Tests.Common.StarWars.TestData; +using GraphQL.Integration.Tests.Helpers; +using Xunit; + +namespace GraphQL.Integration.Tests.APQ; + +[SuppressMessage("ReSharper", "UseConfigureAwaitFalse")] +public class AutomaticPersistentQueriesTest : IAsyncLifetime, IClassFixture +{ + public SystemTextJsonAutoNegotiateServerTestFixture Fixture { get; } + protected GraphQLHttpClient StarWarsClient; + protected GraphQLHttpClient StarWarsWebsocketClient; + + public AutomaticPersistentQueriesTest(SystemTextJsonAutoNegotiateServerTestFixture fixture) + { + Fixture = fixture; + } + + public async Task InitializeAsync() + { + await Fixture.CreateServer(); + StarWarsClient = Fixture.GetStarWarsClient(options => options.EnableAutomaticPersistedQueries = _ => true); + StarWarsWebsocketClient = Fixture.GetStarWarsClient(options => + { + options.EnableAutomaticPersistedQueries = _ => true; + options.UseWebSocketForQueriesAndMutations = true; + }); + } + + public Task DisposeAsync() + { + StarWarsClient?.Dispose(); + return Task.CompletedTask; + } + + [Theory] + [ClassData(typeof(StarWarsHumans))] + public async void After_querying_all_starwars_humans_the_APQDisabledForSession_is_still_false_Async(int id, string name) + { + var query = new GraphQLQuery(""" + query Human($id: String!){ + human(id: $id) { + name + } + } + """); + + var graphQLRequest = new GraphQLRequest(query, new { id = id.ToString() }); + + var response = await StarWarsClient.SendQueryAsync(graphQLRequest, () => new { Human = new { Name = string.Empty } }); + + Assert.Null(response.Errors); + Assert.Equal(name, response.Data.Human.Name); + StarWarsClient.APQDisabledForSession.Should().BeFalse("if APQ has worked it won't get disabled"); + } + + [Theory] + [ClassData(typeof(StarWarsHumans))] + public async void After_querying_all_starwars_humans_using_websocket_transport_the_APQDisabledForSession_is_still_false_Async(int id, string name) + { + var query = new GraphQLQuery(""" + query Human($id: String!){ + human(id: $id) { + name + } + } + """); + + var graphQLRequest = new GraphQLRequest(query, new { id = id.ToString() }); + + var response = await StarWarsWebsocketClient.SendQueryAsync(graphQLRequest, () => new { Human = new { Name = string.Empty } }); + + Assert.Null(response.Errors); + Assert.Equal(name, response.Data.Human.Name); + StarWarsWebsocketClient.APQDisabledForSession.Should().BeFalse("if APQ has worked it won't get disabled"); + } + + [Fact] + public void Verify_the_persisted_query_extension_object() + { + var query = new GraphQLQuery(""" + query Human($id: String!){ + human(id: $id) { + name + } + } + """); + query.Sha256Hash.Should().NotBeNullOrEmpty(); + + var request = new GraphQLRequest(query); + request.Extensions.Should().BeNull(); + request.GeneratePersistedQueryExtension(); + request.Extensions.Should().NotBeNull(); + + string expectedKey = "persistedQuery"; + var expectedExtensionValue = new Dictionary + { + ["version"] = 1, + ["sha256Hash"] = query.Sha256Hash, + }; + + request.Extensions.Should().ContainKey(expectedKey); + request.Extensions![expectedKey].As>() + .Should().NotBeNull().And.BeEquivalentTo(expectedExtensionValue); + } +} diff --git a/tests/IntegrationTestServer/Startup.cs b/tests/IntegrationTestServer/Startup.cs index ee8526c1..a907d623 100644 --- a/tests/IntegrationTestServer/Startup.cs +++ b/tests/IntegrationTestServer/Startup.cs @@ -38,6 +38,7 @@ public void ConfigureServices(IServiceCollection services) }) .AddErrorInfoProvider(opt => opt.ExposeExceptionDetails = Environment.IsDevelopment()) .AddSystemTextJson() + .UseAutomaticPersistedQueries(options => options.TrackLinkedCacheEntries = true) .AddGraphTypes(typeof(ChatSchema).Assembly)); }