Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add GraphQLQuery record type for reusable query declarations with syntax highlighting #638

Merged
merged 5 commits into from
Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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