diff --git a/README.md b/README.md index 6dc83af6..fe859ced 100644 --- a/README.md +++ b/README.md @@ -14,34 +14,38 @@ Provides the following packages: | GraphQL.Client.Serializer.SystemTextJson | [![Nuget](https://img.shields.io/nuget/dt/GraphQL.Client.Serializer.SystemTextJson)](https://www.nuget.org/packages/GraphQL.Client.Serializer.SystemTextJson) | [![Nuget](https://img.shields.io/nuget/vpre/GraphQL.Client.Serializer.SystemTextJson)](https://www.nuget.org/packages/GraphQL.Client.Serializer.SystemTextJson) | | GraphQL.Primitives | [![Nuget](https://img.shields.io/nuget/dt/GraphQL.Primitives)](https://www.nuget.org/packages/GraphQL.Primitives/) | [![Nuget](https://img.shields.io/nuget/vpre/GraphQL.Primitives)](https://www.nuget.org/packages/GraphQL.Primitives) | -## Specification: +## Specification The Library will try to follow the following standards and documents: * [GraphQL Specification](https://spec.graphql.org/June2018/) * [GraphQL HomePage](https://graphql.org/learn) -## Usage: +## Usage ### Create a GraphQLHttpClient ```csharp -// To use NewtonsoftJsonSerializer, add a reference to NuGet package GraphQL.Client.Serializer.Newtonsoft -var graphQLClient = new GraphQLHttpClient("https://api.example.com/graphql", new NewtonsoftJsonSerializer()); +// To use NewtonsoftJsonSerializer, add a reference to +// NuGet package GraphQL.Client.Serializer.Newtonsoft +var graphQLClient = new GraphQLHttpClient( + "https://api.example.com/graphql", + new NewtonsoftJsonSerializer()); ``` > [!NOTE] -> *GraphQLHttpClient* is meant to be used as a single longlived instance per endpoint (i.e. register as singleton in a DI system), which should be reused for multiple requests. +> *GraphQLHttpClient* is meant to be used as a single long-lived instance per endpoint (i.e. register as singleton in a DI system), which should be reused for multiple requests. ### Create a GraphQLRequest: #### Simple Request: ```csharp var heroRequest = new GraphQLRequest { - Query = @" + Query = """ { hero { name } - }" + } + """ }; ``` @@ -49,7 +53,7 @@ var heroRequest = new GraphQLRequest { ```csharp var personAndFilmsRequest = new GraphQLRequest { - Query =@" + Query =""" query PersonAndFilms($id: ID) { person(id: $id) { name @@ -59,7 +63,8 @@ var personAndFilmsRequest = new GraphQLRequest { } } } - }", + } + """, OperationName = "PersonAndFilms", Variables = new { id = "cGVvcGxlOjE=" @@ -72,7 +77,7 @@ var personAndFilmsRequest = new GraphQLRequest { > > If you really need to send a *list of bytes* with a `byte[]` as a source, then convert it to a `List` first, which will tell the serializer to output a list of numbers instead of a base64-encoded string. -### Execute Query/Mutation: +### Execute Query/Mutation ```csharp public class ResponseType @@ -102,7 +107,9 @@ var personName = graphQLResponse.Data.Person.Name; Using the extension method for anonymously typed responses (namespace `GraphQL.Client.Abstractions`) you could achieve the same result with the following code: ```csharp -var graphQLResponse = await graphQLClient.SendQueryAsync(personAndFilmsRequest, () => new { person = new PersonType()} ); +var graphQLResponse = await graphQLClient.SendQueryAsync( + personAndFilmsRequest, + () => new { person = new PersonType()}); var personName = graphQLResponse.Data.person.Name; ``` @@ -162,7 +169,29 @@ Currently, there is no native support for GraphQL formatting and syntax highligh For Rider, JetBrains provides a [Plugin](https://plugins.jetbrains.com/plugin/8097-graphql), too. -## Useful Links: +To leverage syntax highlighting in variable declarations, the `GraphQLQuery` value record type is provided: + +```csharp +GraphQLQuery query = new(""" + query PersonAndFilms($id: ID) { + person(id: $id) { + name + filmConnection { + films { + title + } + } + } + } + """); + +var graphQLResponse = await graphQLClient.SendQueryAsync( + query, + "PersonAndFilms", + new { id = "cGVvcGxlOjE=" }); +``` + +## Useful Links * [StarWars Example Server (GitHub)](https://github.com/graphql/swapi-graphql) * [StarWars Example Server (EndPoint)](https://swapi.apis.guru/) diff --git a/src/GraphQL.Client.Abstractions/GraphQL.Client.Abstractions.csproj b/src/GraphQL.Client.Abstractions/GraphQL.Client.Abstractions.csproj index a18177ca..4fa8f98f 100644 --- a/src/GraphQL.Client.Abstractions/GraphQL.Client.Abstractions.csproj +++ b/src/GraphQL.Client.Abstractions/GraphQL.Client.Abstractions.csproj @@ -2,7 +2,7 @@ Abstractions for GraphQL.Client - netstandard2.0;net7.0;net8.0 + netstandard2.0;net6.0;net7.0;net8.0 diff --git a/src/GraphQL.Client.Abstractions/GraphQLClientExtensions.cs b/src/GraphQL.Client.Abstractions/GraphQLClientExtensions.cs index cce6db1a..92b03980 100644 --- a/src/GraphQL.Client.Abstractions/GraphQLClientExtensions.cs +++ b/src/GraphQL.Client.Abstractions/GraphQLClientExtensions.cs @@ -6,22 +6,39 @@ public static class GraphQLClientExtensions { public static Task> SendQueryAsync(this IGraphQLClient client, [StringSyntax("GraphQL")] string query, object? variables = null, - string? operationName = null, Func defineResponseType = null, CancellationToken cancellationToken = default) + string? operationName = null, Func? defineResponseType = null, CancellationToken cancellationToken = default) { _ = defineResponseType; return client.SendQueryAsync(new GraphQLRequest(query, variables, operationName), cancellationToken: cancellationToken); } +#if NET6_0_OR_GREATER + public static Task> SendQueryAsync(this IGraphQLClient client, + GraphQLQuery query, object? variables = null, + string? operationName = null, Func? defineResponseType = null, + CancellationToken cancellationToken = default) + => SendQueryAsync(client, query.Text, variables, operationName, defineResponseType, + cancellationToken); +#endif + public static Task> SendMutationAsync(this IGraphQLClient client, [StringSyntax("GraphQL")] string query, object? variables = null, - string? operationName = null, Func defineResponseType = null, CancellationToken cancellationToken = default) + string? operationName = null, Func? defineResponseType = null, CancellationToken cancellationToken = default) { _ = defineResponseType; return client.SendMutationAsync(new GraphQLRequest(query, variables, operationName), cancellationToken: cancellationToken); } +#if NET6_0_OR_GREATER + public static Task> SendMutationAsync(this IGraphQLClient client, + GraphQLQuery query, object? variables = null, string? operationName = null, Func? defineResponseType = null, + CancellationToken cancellationToken = default) + => SendMutationAsync(client, query.Text, variables, operationName, defineResponseType, + cancellationToken); +#endif + public static Task> SendQueryAsync(this IGraphQLClient client, GraphQLRequest request, Func defineResponseType, CancellationToken cancellationToken = default) { diff --git a/src/GraphQL.Client/GraphQL.Client.csproj b/src/GraphQL.Client/GraphQL.Client.csproj index 241e3bcc..19a2bb7d 100644 --- a/src/GraphQL.Client/GraphQL.Client.csproj +++ b/src/GraphQL.Client/GraphQL.Client.csproj @@ -1,7 +1,7 @@ - netstandard2.0;net461 + netstandard2.0;net461;net6.0;net7.0;net8.0 GraphQL.Client.Http diff --git a/src/GraphQL.Client/GraphQLHttpRequest.cs b/src/GraphQL.Client/GraphQLHttpRequest.cs index b89d1814..3882195a 100644 --- a/src/GraphQL.Client/GraphQLHttpRequest.cs +++ b/src/GraphQL.Client/GraphQLHttpRequest.cs @@ -20,6 +20,13 @@ public GraphQLHttpRequest([StringSyntax("GraphQL")] string query, object? variab { } +#if NET6_0_OR_GREATER + public GraphQLHttpRequest(GraphQLQuery query, object? variables = null, string? operationName = null, Dictionary? extensions = null) + : base(query, variables, operationName, extensions) + { + } +#endif + public GraphQLHttpRequest(GraphQLRequest other) : base(other) { diff --git a/src/GraphQL.Client/GraphQLSubscriptionException.cs b/src/GraphQL.Client/GraphQLSubscriptionException.cs index ccaab426..21dd8982 100644 --- a/src/GraphQL.Client/GraphQLSubscriptionException.cs +++ b/src/GraphQL.Client/GraphQLSubscriptionException.cs @@ -1,17 +1,12 @@ +#if !NET8_0_OR_GREATER using System.Runtime.Serialization; +#endif namespace GraphQL.Client.Http; [Serializable] public class GraphQLSubscriptionException : Exception { - // - // For guidelines regarding the creation of new exception types, see - // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp - // and - // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp - // - public GraphQLSubscriptionException() { } @@ -20,9 +15,11 @@ public GraphQLSubscriptionException(object error) : base(error.ToString()) { } +#if !NET8_0_OR_GREATER protected GraphQLSubscriptionException( SerializationInfo info, StreamingContext context) : base(info, context) { } +#endif } diff --git a/src/GraphQL.Client/Websocket/GraphQLWebsocketConnectionException.cs b/src/GraphQL.Client/Websocket/GraphQLWebsocketConnectionException.cs index f9811b98..aedebb90 100644 --- a/src/GraphQL.Client/Websocket/GraphQLWebsocketConnectionException.cs +++ b/src/GraphQL.Client/Websocket/GraphQLWebsocketConnectionException.cs @@ -1,4 +1,6 @@ +#if !NET8_0_OR_GREATER using System.Runtime.Serialization; +#endif namespace GraphQL.Client.Http.Websocket; @@ -9,15 +11,18 @@ public GraphQLWebsocketConnectionException() { } - protected GraphQLWebsocketConnectionException(SerializationInfo info, StreamingContext context) : base(info, context) + public GraphQLWebsocketConnectionException(string message) : base(message) { } - public GraphQLWebsocketConnectionException(string message) : base(message) + public GraphQLWebsocketConnectionException(string message, Exception innerException) : base(message, innerException) { } - public GraphQLWebsocketConnectionException(string message, Exception innerException) : base(message, innerException) +#if !NET8_0_OR_GREATER + protected GraphQLWebsocketConnectionException(SerializationInfo info, StreamingContext context) : base(info, context) { } +#endif + } diff --git a/src/GraphQL.Primitives/GraphQL.Primitives.csproj b/src/GraphQL.Primitives/GraphQL.Primitives.csproj index b49b7360..44f6e6fe 100644 --- a/src/GraphQL.Primitives/GraphQL.Primitives.csproj +++ b/src/GraphQL.Primitives/GraphQL.Primitives.csproj @@ -3,7 +3,7 @@ GraphQL basic types GraphQL - netstandard2.0;net7.0;net8.0 + netstandard2.0;net6.0;net7.0;net8.0 diff --git a/src/GraphQL.Primitives/GraphQLQuery.cs b/src/GraphQL.Primitives/GraphQLQuery.cs new file mode 100644 index 00000000..b4ccf635 --- /dev/null +++ b/src/GraphQL.Primitives/GraphQLQuery.cs @@ -0,0 +1,15 @@ +#if NET6_0_OR_GREATER +using System.Diagnostics.CodeAnalysis; + +namespace GraphQL; + +/// +/// Value record for a GraphQL query string +/// +/// the actual query string +public readonly record struct GraphQLQuery([StringSyntax("GraphQL")] string Text) +{ + public static implicit operator string(GraphQLQuery query) + => query.Text; +}; +#endif diff --git a/src/GraphQL.Primitives/GraphQLRequest.cs b/src/GraphQL.Primitives/GraphQLRequest.cs index 7aecb656..c208a90f 100644 --- a/src/GraphQL.Primitives/GraphQLRequest.cs +++ b/src/GraphQL.Primitives/GraphQLRequest.cs @@ -59,6 +59,14 @@ public GraphQLRequest([StringSyntax("GraphQL")] string query, object? variables Extensions = extensions; } +#if NET6_0_OR_GREATER + public GraphQLRequest(GraphQLQuery query, object? variables = null, string? operationName = null, + Dictionary? extensions = null) + : this(query.Text, variables, operationName, extensions) + { + } +#endif + public GraphQLRequest(GraphQLRequest other) : base(other) { } /// diff --git a/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs b/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs index cb25adc6..e20fdd39 100644 --- a/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs +++ b/tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs @@ -82,13 +82,15 @@ public async void QueryWithDynamicReturnTypeTheory(int id, string name) [ClassData(typeof(StarWarsHumans))] public async void QueryWitVarsTheory(int id, string name) { - var graphQLRequest = new GraphQLRequest(@" - query Human($id: String!){ - human(id: $id) { - name - } - }", - new { id = id.ToString() }); + var query = new GraphQLQuery(""" + query Human($id: String!){ + human(id: $id) { + name + } + } + """); + + var graphQLRequest = new GraphQLRequest(query, new { id = id.ToString() }); var response = await StarWarsClient.SendQueryAsync(graphQLRequest, () => new { Human = new { Name = string.Empty } });