Skip to content

Commit dbd9c20

Browse files
authored
Add GraphQLQuery record type for reusable query declarations with syntax highlighting (#638)
* add GraphQLQuery record type for reusable query declarations * enable GraphQLQuery record from .NET 6.0 upwards * document GraphQLQuery type * optimize linebreaks in Readme * fix code formatting in readme
1 parent fd2cf06 commit dbd9c20

File tree

11 files changed

+114
-34
lines changed

11 files changed

+114
-34
lines changed

README.md

+41-12
Original file line numberDiff line numberDiff line change
@@ -14,42 +14,46 @@ Provides the following packages:
1414
| 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) |
1515
| 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) |
1616

17-
## Specification:
17+
## Specification
1818
The Library will try to follow the following standards and documents:
1919

2020
* [GraphQL Specification](https://spec.graphql.org/June2018/)
2121
* [GraphQL HomePage](https://graphql.org/learn)
2222

23-
## Usage:
23+
## Usage
2424

2525
### Create a GraphQLHttpClient
2626

2727
```csharp
28-
// To use NewtonsoftJsonSerializer, add a reference to NuGet package GraphQL.Client.Serializer.Newtonsoft
29-
var graphQLClient = new GraphQLHttpClient("https://api.example.com/graphql", new NewtonsoftJsonSerializer());
28+
// To use NewtonsoftJsonSerializer, add a reference to
29+
// NuGet package GraphQL.Client.Serializer.Newtonsoft
30+
var graphQLClient = new GraphQLHttpClient(
31+
"https://api.example.com/graphql",
32+
new NewtonsoftJsonSerializer());
3033
```
3134

3235
> [!NOTE]
33-
> *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.
36+
> *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.
3437
3538
### Create a GraphQLRequest:
3639
#### Simple Request:
3740
```csharp
3841
var heroRequest = new GraphQLRequest {
39-
Query = @"
42+
Query = """
4043
{
4144
hero {
4245
name
4346
}
44-
}"
47+
}
48+
"""
4549
};
4650
```
4751

4852
#### OperationName and Variables Request:
4953

5054
```csharp
5155
var personAndFilmsRequest = new GraphQLRequest {
52-
Query =@"
56+
Query ="""
5357
query PersonAndFilms($id: ID) {
5458
person(id: $id) {
5559
name
@@ -59,7 +63,8 @@ var personAndFilmsRequest = new GraphQLRequest {
5963
}
6064
}
6165
}
62-
}",
66+
}
67+
""",
6368
OperationName = "PersonAndFilms",
6469
Variables = new {
6570
id = "cGVvcGxlOjE="
@@ -72,7 +77,7 @@ var personAndFilmsRequest = new GraphQLRequest {
7277
>
7378
> If you really need to send a *list of bytes* with a `byte[]` as a source, then convert it to a `List<byte>` first, which will tell the serializer to output a list of numbers instead of a base64-encoded string.
7479
75-
### Execute Query/Mutation:
80+
### Execute Query/Mutation
7681

7782
```csharp
7883
public class ResponseType
@@ -102,7 +107,9 @@ var personName = graphQLResponse.Data.Person.Name;
102107
Using the extension method for anonymously typed responses (namespace `GraphQL.Client.Abstractions`) you could achieve the same result with the following code:
103108

104109
```csharp
105-
var graphQLResponse = await graphQLClient.SendQueryAsync(personAndFilmsRequest, () => new { person = new PersonType()} );
110+
var graphQLResponse = await graphQLClient.SendQueryAsync(
111+
personAndFilmsRequest,
112+
() => new { person = new PersonType()});
106113
var personName = graphQLResponse.Data.person.Name;
107114
```
108115

@@ -162,7 +169,29 @@ Currently, there is no native support for GraphQL formatting and syntax highligh
162169

163170
For Rider, JetBrains provides a [Plugin](https://plugins.jetbrains.com/plugin/8097-graphql), too.
164171

165-
## Useful Links:
172+
To leverage syntax highlighting in variable declarations, the `GraphQLQuery` value record type is provided:
173+
174+
```csharp
175+
GraphQLQuery query = new("""
176+
query PersonAndFilms($id: ID) {
177+
person(id: $id) {
178+
name
179+
filmConnection {
180+
films {
181+
title
182+
}
183+
}
184+
}
185+
}
186+
""");
187+
188+
var graphQLResponse = await graphQLClient.SendQueryAsync<ResponseType>(
189+
query,
190+
"PersonAndFilms",
191+
new { id = "cGVvcGxlOjE=" });
192+
```
193+
194+
## Useful Links
166195

167196
* [StarWars Example Server (GitHub)](https://github.com/graphql/swapi-graphql)
168197
* [StarWars Example Server (EndPoint)](https://swapi.apis.guru/)

src/GraphQL.Client.Abstractions/GraphQL.Client.Abstractions.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<Description>Abstractions for GraphQL.Client</Description>
5-
<TargetFrameworks>netstandard2.0;net7.0;net8.0</TargetFrameworks>
5+
<TargetFrameworks>netstandard2.0;net6.0;net7.0;net8.0</TargetFrameworks>
66
</PropertyGroup>
77

88
<ItemGroup>

src/GraphQL.Client.Abstractions/GraphQLClientExtensions.cs

+19-2
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,39 @@ public static class GraphQLClientExtensions
66
{
77
public static Task<GraphQLResponse<TResponse>> SendQueryAsync<TResponse>(this IGraphQLClient client,
88
[StringSyntax("GraphQL")] string query, object? variables = null,
9-
string? operationName = null, Func<TResponse> defineResponseType = null, CancellationToken cancellationToken = default)
9+
string? operationName = null, Func<TResponse>? defineResponseType = null, CancellationToken cancellationToken = default)
1010
{
1111
_ = defineResponseType;
1212
return client.SendQueryAsync<TResponse>(new GraphQLRequest(query, variables, operationName),
1313
cancellationToken: cancellationToken);
1414
}
1515

16+
#if NET6_0_OR_GREATER
17+
public static Task<GraphQLResponse<TResponse>> SendQueryAsync<TResponse>(this IGraphQLClient client,
18+
GraphQLQuery query, object? variables = null,
19+
string? operationName = null, Func<TResponse>? defineResponseType = null,
20+
CancellationToken cancellationToken = default)
21+
=> SendQueryAsync(client, query.Text, variables, operationName, defineResponseType,
22+
cancellationToken);
23+
#endif
24+
1625
public static Task<GraphQLResponse<TResponse>> SendMutationAsync<TResponse>(this IGraphQLClient client,
1726
[StringSyntax("GraphQL")] string query, object? variables = null,
18-
string? operationName = null, Func<TResponse> defineResponseType = null, CancellationToken cancellationToken = default)
27+
string? operationName = null, Func<TResponse>? defineResponseType = null, CancellationToken cancellationToken = default)
1928
{
2029
_ = defineResponseType;
2130
return client.SendMutationAsync<TResponse>(new GraphQLRequest(query, variables, operationName),
2231
cancellationToken: cancellationToken);
2332
}
2433

34+
#if NET6_0_OR_GREATER
35+
public static Task<GraphQLResponse<TResponse>> SendMutationAsync<TResponse>(this IGraphQLClient client,
36+
GraphQLQuery query, object? variables = null, string? operationName = null, Func<TResponse>? defineResponseType = null,
37+
CancellationToken cancellationToken = default)
38+
=> SendMutationAsync(client, query.Text, variables, operationName, defineResponseType,
39+
cancellationToken);
40+
#endif
41+
2542
public static Task<GraphQLResponse<TResponse>> SendQueryAsync<TResponse>(this IGraphQLClient client,
2643
GraphQLRequest request, Func<TResponse> defineResponseType, CancellationToken cancellationToken = default)
2744
{

src/GraphQL.Client/GraphQL.Client.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFrameworks>netstandard2.0;net461</TargetFrameworks>
4+
<TargetFrameworks>netstandard2.0;net461;net6.0;net7.0;net8.0</TargetFrameworks>
55
<RootNamespace>GraphQL.Client.Http</RootNamespace>
66
</PropertyGroup>
77

src/GraphQL.Client/GraphQLHttpRequest.cs

+7
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ public GraphQLHttpRequest([StringSyntax("GraphQL")] string query, object? variab
2020
{
2121
}
2222

23+
#if NET6_0_OR_GREATER
24+
public GraphQLHttpRequest(GraphQLQuery query, object? variables = null, string? operationName = null, Dictionary<string, object?>? extensions = null)
25+
: base(query, variables, operationName, extensions)
26+
{
27+
}
28+
#endif
29+
2330
public GraphQLHttpRequest(GraphQLRequest other)
2431
: base(other)
2532
{
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
1+
#if !NET8_0_OR_GREATER
12
using System.Runtime.Serialization;
3+
#endif
24

35
namespace GraphQL.Client.Http;
46

57
[Serializable]
68
public class GraphQLSubscriptionException : Exception
79
{
8-
//
9-
// For guidelines regarding the creation of new exception types, see
10-
// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp
11-
// and
12-
// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp
13-
//
14-
1510
public GraphQLSubscriptionException()
1611
{
1712
}
@@ -20,9 +15,11 @@ public GraphQLSubscriptionException(object error) : base(error.ToString())
2015
{
2116
}
2217

18+
#if !NET8_0_OR_GREATER
2319
protected GraphQLSubscriptionException(
2420
SerializationInfo info,
2521
StreamingContext context) : base(info, context)
2622
{
2723
}
24+
#endif
2825
}
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
#if !NET8_0_OR_GREATER
12
using System.Runtime.Serialization;
3+
#endif
24

35
namespace GraphQL.Client.Http.Websocket;
46

@@ -9,15 +11,18 @@ public GraphQLWebsocketConnectionException()
911
{
1012
}
1113

12-
protected GraphQLWebsocketConnectionException(SerializationInfo info, StreamingContext context) : base(info, context)
14+
public GraphQLWebsocketConnectionException(string message) : base(message)
1315
{
1416
}
1517

16-
public GraphQLWebsocketConnectionException(string message) : base(message)
18+
public GraphQLWebsocketConnectionException(string message, Exception innerException) : base(message, innerException)
1719
{
1820
}
1921

20-
public GraphQLWebsocketConnectionException(string message, Exception innerException) : base(message, innerException)
22+
#if !NET8_0_OR_GREATER
23+
protected GraphQLWebsocketConnectionException(SerializationInfo info, StreamingContext context) : base(info, context)
2124
{
2225
}
26+
#endif
27+
2328
}

src/GraphQL.Primitives/GraphQL.Primitives.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<PropertyGroup>
44
<Description>GraphQL basic types</Description>
55
<RootNamespace>GraphQL</RootNamespace>
6-
<TargetFrameworks>netstandard2.0;net7.0;net8.0</TargetFrameworks>
6+
<TargetFrameworks>netstandard2.0;net6.0;net7.0;net8.0</TargetFrameworks>
77
</PropertyGroup>
88

99
</Project>
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#if NET6_0_OR_GREATER
2+
using System.Diagnostics.CodeAnalysis;
3+
4+
namespace GraphQL;
5+
6+
/// <summary>
7+
/// Value record for a GraphQL query string
8+
/// </summary>
9+
/// <param name="Text">the actual query string</param>
10+
public readonly record struct GraphQLQuery([StringSyntax("GraphQL")] string Text)
11+
{
12+
public static implicit operator string(GraphQLQuery query)
13+
=> query.Text;
14+
};
15+
#endif

src/GraphQL.Primitives/GraphQLRequest.cs

+8
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,14 @@ public GraphQLRequest([StringSyntax("GraphQL")] string query, object? variables
5959
Extensions = extensions;
6060
}
6161

62+
#if NET6_0_OR_GREATER
63+
public GraphQLRequest(GraphQLQuery query, object? variables = null, string? operationName = null,
64+
Dictionary<string, object?>? extensions = null)
65+
: this(query.Text, variables, operationName, extensions)
66+
{
67+
}
68+
#endif
69+
6270
public GraphQLRequest(GraphQLRequest other) : base(other) { }
6371

6472
/// <summary>

tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs

+9-7
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,15 @@ public async void QueryWithDynamicReturnTypeTheory(int id, string name)
8282
[ClassData(typeof(StarWarsHumans))]
8383
public async void QueryWitVarsTheory(int id, string name)
8484
{
85-
var graphQLRequest = new GraphQLRequest(@"
86-
query Human($id: String!){
87-
human(id: $id) {
88-
name
89-
}
90-
}",
91-
new { id = id.ToString() });
85+
var query = new GraphQLQuery("""
86+
query Human($id: String!){
87+
human(id: $id) {
88+
name
89+
}
90+
}
91+
""");
92+
93+
var graphQLRequest = new GraphQLRequest(query, new { id = id.ToString() });
9294

9395
var response = await StarWarsClient.SendQueryAsync(graphQLRequest, () => new { Human = new { Name = string.Empty } });
9496

0 commit comments

Comments
 (0)