Skip to content

Commit

Permalink
Merge branch 'release/1.0.0-beta1'
Browse files Browse the repository at this point in the history
  • Loading branch information
sahb1239 committed Jan 10, 2018
2 parents 50f72ef + 3be427d commit e435720
Show file tree
Hide file tree
Showing 51 changed files with 1,235 additions and 476 deletions.
16 changes: 6 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@ An example for the Starwars API.
```csharp
// TODO: Use dependency injection (services.AddGraphQLHttpClient()) (IServiceCollection)
// Initilize GraphQLClient
IGraphQLFieldBuilder fieldBuilder = new GraphQLFieldBuilder();
IGraphQLQueryBuilder queryBuilder = new GraphQLQueryBuilder(fieldBuilder);
IGraphQLHttpExecutor executor = new GraphQLHttpExecutor();
IGraphQLHttpClient client = new GraphQLHttpClient(executor, queryBuilder);
IGraphQLHttpClient client = GraphQLHttpClient.Default();

// Get response from url
var response = await client.Query<Query>("https://mpjk0plp9.lp.gql.zone/graphql");
Expand Down Expand Up @@ -52,9 +49,8 @@ The following code requests the endpoint with the following query

The following using statements is required
```csharp
using SAHB.GraphQLClient.Executor;
using SAHB.GraphQLClient.FieldBuilder;
using SAHB.GraphQLClient.QueryBuilder;
using SAHB.GraphQLClient;
using SAHB.GraphQLClient.Extentions;
```

### Renaming of a field
Expand Down Expand Up @@ -122,20 +118,20 @@ public class Query
```

### Arguments
It's also possible to add arguments to queries. This can be done with the attribute ```GraphQLArgumentAttribute```. This attribute takes 2 arguments where the first is argument name used on the GraphQL server. The second argument is the varible name which should be used when the query is requested.
It's also possible to add arguments to queries. This can be done with the attribute ```GraphQLArgumentAttribute```. This attribute takes 3 arguments where the first is argument name used on the GraphQL server. The second is the argument type, for example String. The third argument is the varible name which should be used when the query is requested.

```csharp
public class Query
{
[GraphQLArgumentAttribute("argumentName", "variableName")]
[GraphQLArgumentAttribute("argumentName", "ArgumentType", "variableName")]
public Hero Hero { get; set; }
}
```

The client is requested as shown here:
```csharp
var response = await client.Query<Query>("https://mpjk0plp9.lp.gql.zone/graphql",
arguments: new GraphQLQueryArgument("variableName", "String", "valueToBeSent"});
arguments: new GraphQLQueryArgument("variableName", "valueToBeSent"});
```

This will generate the query (Hero contains here only the Name property):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@
<ProjectReference Include="..\..\tests\SAHB.GraphQLClient.Tests\SAHB.GraphQLClient.Tests.csproj" />
</ItemGroup>

<ItemGroup>
<Folder Include="BenchmarkDotNet.Artifacts\" />
</ItemGroup>

</Project>
24 changes: 24 additions & 0 deletions src/SAHB.GraphQLClient/Batching/IGraphQLBatch.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using SAHB.GraphQLClient.QueryGenerator;
namespace SAHB.GraphQLClient.Batching
{
// ReSharper disable once InconsistentNaming
/// <summary>
/// GraphQL batch which supports querying multiple queries at one time
/// </summary>
public interface IGraphQLBatch
{
/// <summary>
/// Generates a query to a GraphQL server using a specified type and the GraphQL argument variables
/// </summary>
/// <typeparam name="T">The type to generate the query from</typeparam>
/// <param name="arguments">The arguments used in the query which is inserted in the variables</param>
/// <returns></returns>
IGraphQLQuery<T> Query<T>(params GraphQLQueryArgument[] arguments) where T : class;

/// <summary>
/// Returns true if the batch has been executed, when executed its not possible to add more queries to it
/// </summary>
/// <returns>Returns if the batch has been executed</returns>
bool IsExecuted();
}
}
Expand Down
38 changes: 38 additions & 0 deletions src/SAHB.GraphQLClient/Batching/Internal/GraphQLBatch.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System;
using System.Net.Http;
using SAHB.GraphQLClient.Exceptions;
using SAHB.GraphQLClient.Executor;
using SAHB.GraphQLClient.FieldBuilder;
using SAHB.GraphQLClient.QueryGenerator;

namespace SAHB.GraphQLClient.Batching.Internal
{
// ReSharper disable once InconsistentNaming
/// <inheritdoc />
internal class GraphQLBatch : IGraphQLBatch
{
private readonly GraphQLBatchMerger _batch;

internal GraphQLBatch(string url, HttpMethod httpMethod, string authorizationToken, string authorizationMethod, IGraphQLHttpExecutor executor, IGraphQLFieldBuilder fieldBuilder, IGraphQLQueryGeneratorFromFields queryGenerator)
{
_batch = new GraphQLBatchMerger(url, httpMethod, authorizationToken, authorizationMethod, executor, fieldBuilder, queryGenerator);
}

/// <inheritdoc />
public IGraphQLQuery<T> Query<T>(params GraphQLQueryArgument[] arguments) where T : class
{
if (_batch.Executed)
{
throw new GraphQLBatchAlreadyExecutedException();
}

return _batch.AddQuery<T>(arguments);
}

/// <inheritdoc />
public bool IsExecuted()
{
return _batch.Executed;
}
}
}
125 changes: 125 additions & 0 deletions src/SAHB.GraphQLClient/Batching/Internal/GraphQLBatchMerger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using SAHB.GraphQLClient.Executor;
using SAHB.GraphQLClient.FieldBuilder;
using SAHB.GraphQLClient.QueryGenerator;
using SAHB.GraphQLClient.Result;

namespace SAHB.GraphQLClient.Batching.Internal
{
// ReSharper disable once InconsistentNaming
internal class GraphQLBatchMerger
{
private readonly string _url;
private readonly HttpMethod _httpMethod;
private readonly string _authorizationToken;
private readonly string _authorizationMethod;
private readonly IGraphQLHttpExecutor _executor;
private readonly IGraphQLFieldBuilder _fieldBuilder;
private readonly IGraphQLQueryGeneratorFromFields _queryGenerator;
private readonly IDictionary<string, IEnumerable<GraphQLFieldWithOverridedAlias>> _fields;
private readonly IDictionary<string, GraphQLQueryArgument[]> _arguments;
private int _identifierCount = 0;
private bool _isExecuted = false;
private GraphQLDataResult<JObject> _result;

public GraphQLBatchMerger(string url, HttpMethod httpMethod, string authorizationToken, string authorizationMethod, IGraphQLHttpExecutor executor, IGraphQLFieldBuilder fieldBuilder, IGraphQLQueryGeneratorFromFields queryGenerator)
{
_url = url;
_httpMethod = httpMethod;
_authorizationToken = authorizationToken;
_authorizationMethod = authorizationMethod;
_executor = executor;
_fieldBuilder = fieldBuilder;
_queryGenerator = queryGenerator;
_fields = new Dictionary<string, IEnumerable<GraphQLFieldWithOverridedAlias>>();
_arguments = new Dictionary<string, GraphQLQueryArgument[]>();
}

public IGraphQLQuery<T> AddQuery<T>(params GraphQLQueryArgument[] arguments)
where T : class
{
if (_isExecuted)
throw new NotSupportedException("Cannot add query to already executed batch");

var identifier = $"batch{_identifierCount++}";

// Add fields
_fields.Add(identifier,
_fieldBuilder.GetFields(typeof(T)).Select(field =>
new GraphQLFieldWithOverridedAlias(
identifier + "_" + (string.IsNullOrWhiteSpace(field.Alias) ? field.Field : field.Alias),
field)));
_arguments.Add(identifier, arguments);

return new GraphQLBatchQuery<T>(this, identifier);
}

public async Task<T> GetValue<T>(string identitifer)
where T : class
{
if (!_isExecuted)
await Execute();

// Create new JObject
JObject deserilizeFrom = new JObject();

// Get all fields
foreach (var field in _fields[identitifer])
{
// Add field with previous alias to JObject
deserilizeFrom.Add(field.Inner.Alias, _result.Data[field.Alias]);
}

// Deserilize from
return deserilizeFrom.ToObject<T>();
}

public async Task Execute()
{
if (_isExecuted)
throw new NotSupportedException("Batch is already executed");

_isExecuted = true;

// Check if all variable names for arguments which contains dublicates
if (_arguments.SelectMany(e => e.Value).GroupBy(e => e.VariableName).Any(e => e.Count() > 1))
{
UpdateArguments();
}

// Generate query
var query = _queryGenerator.GetQuery(_fields.SelectMany(e => e.Value),
_arguments.SelectMany(e => e.Value).ToArray());

// Execute query
_result =
await _executor.ExecuteQuery<JObject>(query, _url, _httpMethod, _authorizationToken, _authorizationMethod);
}

private void UpdateArguments()
{
throw new NotImplementedException("Arguments with same variable names is not supported at the moment");
}

public bool Executed => _isExecuted;

// ReSharper disable once InconsistentNaming
/// <inheritdoc />
private class GraphQLFieldWithOverridedAlias : GraphQLField
{
public GraphQLFieldWithOverridedAlias(string alias, GraphQLField field)
: base(alias, field: field.Field, fields: field.Fields,
arguments: field.Arguments)
{
Inner = field;
}

public GraphQLField Inner { get; }
}
}
}
25 changes: 25 additions & 0 deletions src/SAHB.GraphQLClient/Batching/Internal/GraphQLBatchQuery.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.Threading.Tasks;

namespace SAHB.GraphQLClient.Batching.Internal
{
// ReSharper disable once InconsistentNaming
/// <inheritdoc />
internal class GraphQLBatchQuery<T> : IGraphQLQuery<T>
where T : class
{
private readonly GraphQLBatchMerger _batch;
private readonly string _identitifer;

internal GraphQLBatchQuery(GraphQLBatchMerger batch, string identitifer)
{
_batch = batch;
_identitifer = identitifer;
}

/// <inheritdoc />
public Task<T> Execute()
{
return _batch.GetValue<T>(_identitifer);
}
}
}
26 changes: 26 additions & 0 deletions src/SAHB.GraphQLClient/Builder/IGraphQLBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;

namespace SAHB.GraphQLClient.Builder
{
// ReSharper disable once InconsistentNaming
/// <summary>
/// Contains methods for building a GraphQL query
/// </summary>
public interface IGraphQLBuilder
{
/// <summary>
/// Creates a field in the query with the field name
/// </summary>
/// <param name="field">The graphQL field to request from the server</param>
/// <returns>Returns the same instance</returns>
IGraphQLBuilder Field(string field);

/// <summary>
/// Creates a field in the query with the field name
/// </summary>
/// <param name="field">The graphQL field to request from the server</param>
/// <param name="generator">Configure the field</param>
/// <returns>Returns the same instance</returns>
IGraphQLBuilder Field(string field, Action<IGraphQLQueryFieldBuilder> generator);
}
}
25 changes: 25 additions & 0 deletions src/SAHB.GraphQLClient/Builder/IGraphQLQueryFieldBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace SAHB.GraphQLClient.Builder
{
// ReSharper disable once InconsistentNaming
/// <summary>
/// Contains methods for building a GraphQL field
/// </summary>
public interface IGraphQLQueryFieldBuilder : IGraphQLBuilder
{
/// <summary>
/// Sets the alias of the field
/// </summary>
/// <param name="alias">The alias to use for the field</param>
/// <returns>Returns the same instance</returns>
IGraphQLQueryFieldBuilder Alias(string alias);

/// <summary>
/// Adds a argument to the field
/// </summary>
/// <param name="argumentName">The argument name used on the GraphQL server</param>
/// <param name="argumentType">The argument type used on the GraphQL server, for example Int or String</param>
/// <param name="variableName">The variable used to execute the query</param>
/// <returns>Returns the same instance</returns>
IGraphQLQueryFieldBuilder Argument(string argumentName, string argumentType, string variableName);
}
}
33 changes: 33 additions & 0 deletions src/SAHB.GraphQLClient/Builder/Internal/GraphQLBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using SAHB.GraphQLClient.FieldBuilder;

namespace SAHB.GraphQLClient.Builder.Internal
{
// ReSharper disable once InconsistentNaming
internal class GraphQLBuilder : IGraphQLBuilder
{
protected List<GraphQLQueryFieldBuilder> Fields = new List<GraphQLQueryFieldBuilder>();

/// <inheritdoc />
public IGraphQLBuilder Field(string field)
{
return Field(field, null);
}

/// <inheritdoc />
public IGraphQLBuilder Field(string field, Action<IGraphQLQueryFieldBuilder> generator)
{
var fieldGen = new GraphQLQueryFieldBuilder(field);
generator?.Invoke(fieldGen);
Fields.Add(fieldGen);
return this;
}

public IEnumerable<GraphQLField> GetFields()
{
return Fields.Select(field => field.GetField());
}
}
}
Loading

0 comments on commit e435720

Please sign in to comment.