From 9c704a94e48639de0d6181a61e49c82d3ea4a3e2 Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Thu, 21 Jul 2022 07:35:06 +0300 Subject: [PATCH] Convert to file-scoped namespaces, no code changes --- .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); } }