Skip to content

Commit

Permalink
Add GraphQLQuery record type for reusable query declarations with syn…
Browse files Browse the repository at this point in the history
…tax 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
  • Loading branch information
rose-a authored Apr 22, 2024
1 parent fd2cf06 commit dbd9c20
Show file tree
Hide file tree
Showing 11 changed files with 114 additions and 34 deletions.
53 changes: 41 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,42 +14,46 @@ 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
}
}"
}
"""
};
```

#### OperationName and Variables Request:

```csharp
var personAndFilmsRequest = new GraphQLRequest {
Query =@"
Query ="""
query PersonAndFilms($id: ID) {
person(id: $id) {
name
Expand All @@ -59,7 +63,8 @@ var personAndFilmsRequest = new GraphQLRequest {
}
}
}
}",
}
""",
OperationName = "PersonAndFilms",
Variables = new {
id = "cGVvcGxlOjE="
Expand All @@ -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<byte>` 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
Expand Down Expand Up @@ -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;
```

Expand Down Expand Up @@ -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<ResponseType>(
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/)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<Description>Abstractions for GraphQL.Client</Description>
<TargetFrameworks>netstandard2.0;net7.0;net8.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0;net6.0;net7.0;net8.0</TargetFrameworks>
</PropertyGroup>

<ItemGroup>
Expand Down
21 changes: 19 additions & 2 deletions src/GraphQL.Client.Abstractions/GraphQLClientExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,39 @@ public static class GraphQLClientExtensions
{
public static Task<GraphQLResponse<TResponse>> SendQueryAsync<TResponse>(this IGraphQLClient client,
[StringSyntax("GraphQL")] string query, object? variables = null,
string? operationName = null, Func<TResponse> defineResponseType = null, CancellationToken cancellationToken = default)
string? operationName = null, Func<TResponse>? defineResponseType = null, CancellationToken cancellationToken = default)
{
_ = defineResponseType;
return client.SendQueryAsync<TResponse>(new GraphQLRequest(query, variables, operationName),
cancellationToken: cancellationToken);
}

#if NET6_0_OR_GREATER
public static Task<GraphQLResponse<TResponse>> SendQueryAsync<TResponse>(this IGraphQLClient client,
GraphQLQuery query, object? variables = null,
string? operationName = null, Func<TResponse>? defineResponseType = null,
CancellationToken cancellationToken = default)
=> SendQueryAsync(client, query.Text, variables, operationName, defineResponseType,
cancellationToken);
#endif

public static Task<GraphQLResponse<TResponse>> SendMutationAsync<TResponse>(this IGraphQLClient client,
[StringSyntax("GraphQL")] string query, object? variables = null,
string? operationName = null, Func<TResponse> defineResponseType = null, CancellationToken cancellationToken = default)
string? operationName = null, Func<TResponse>? defineResponseType = null, CancellationToken cancellationToken = default)
{
_ = defineResponseType;
return client.SendMutationAsync<TResponse>(new GraphQLRequest(query, variables, operationName),
cancellationToken: cancellationToken);
}

#if NET6_0_OR_GREATER
public static Task<GraphQLResponse<TResponse>> SendMutationAsync<TResponse>(this IGraphQLClient client,
GraphQLQuery query, object? variables = null, string? operationName = null, Func<TResponse>? defineResponseType = null,
CancellationToken cancellationToken = default)
=> SendMutationAsync(client, query.Text, variables, operationName, defineResponseType,
cancellationToken);
#endif

public static Task<GraphQLResponse<TResponse>> SendQueryAsync<TResponse>(this IGraphQLClient client,
GraphQLRequest request, Func<TResponse> defineResponseType, CancellationToken cancellationToken = default)
{
Expand Down
2 changes: 1 addition & 1 deletion src/GraphQL.Client/GraphQL.Client.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard2.0;net461</TargetFrameworks>
<TargetFrameworks>netstandard2.0;net461;net6.0;net7.0;net8.0</TargetFrameworks>
<RootNamespace>GraphQL.Client.Http</RootNamespace>
</PropertyGroup>

Expand Down
7 changes: 7 additions & 0 deletions src/GraphQL.Client/GraphQLHttpRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, object?>? extensions = null)
: base(query, variables, operationName, extensions)
{
}
#endif

public GraphQLHttpRequest(GraphQLRequest other)
: base(other)
{
Expand Down
11 changes: 4 additions & 7 deletions src/GraphQL.Client/GraphQLSubscriptionException.cs
Original file line number Diff line number Diff line change
@@ -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()
{
}
Expand All @@ -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
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#if !NET8_0_OR_GREATER
using System.Runtime.Serialization;
#endif

namespace GraphQL.Client.Http.Websocket;

Expand All @@ -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

}
2 changes: 1 addition & 1 deletion src/GraphQL.Primitives/GraphQL.Primitives.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<Description>GraphQL basic types</Description>
<RootNamespace>GraphQL</RootNamespace>
<TargetFrameworks>netstandard2.0;net7.0;net8.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0;net6.0;net7.0;net8.0</TargetFrameworks>
</PropertyGroup>

</Project>
15 changes: 15 additions & 0 deletions src/GraphQL.Primitives/GraphQLQuery.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#if NET6_0_OR_GREATER
using System.Diagnostics.CodeAnalysis;

namespace GraphQL;

/// <summary>
/// Value record for a GraphQL query string
/// </summary>
/// <param name="Text">the actual query string</param>
public readonly record struct GraphQLQuery([StringSyntax("GraphQL")] string Text)
{
public static implicit operator string(GraphQLQuery query)
=> query.Text;
};
#endif
8 changes: 8 additions & 0 deletions src/GraphQL.Primitives/GraphQLRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, object?>? extensions = null)
: this(query.Text, variables, operationName, extensions)
{
}
#endif

public GraphQLRequest(GraphQLRequest other) : base(other) { }

/// <summary>
Expand Down
16 changes: 9 additions & 7 deletions tests/GraphQL.Integration.Tests/QueryAndMutationTests/Base.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 } });

Expand Down

0 comments on commit dbd9c20

Please sign in to comment.