Skip to content

Commit

Permalink
Make it easer to implement connections for new data sources (V10). (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelstaib committed May 18, 2020
1 parent ae2c62d commit 7e0b24b
Show file tree
Hide file tree
Showing 39 changed files with 1,042 additions and 588 deletions.
3 changes: 1 addition & 2 deletions src/Core/Core/Execution/Utilities/ResolverContext.cs
Expand Up @@ -120,8 +120,7 @@ public T Parent<T>()
return parent;
}

if (_executionContext.Converter
.TryConvert<object, T>(SourceObject, out parent))
if (_executionContext.Converter.TryConvert<object, T>(SourceObject, out parent))
{
return parent;
}
Expand Down
13 changes: 1 addition & 12 deletions src/Core/Types.Filters/QueryableFilterMiddleware.cs
Expand Up @@ -35,15 +35,6 @@ public async Task InvokeAsync(IMiddlewareContext context)

IQueryable<T> source = null;

if (context.Result is PageableData<T> p)
{
source = p.Source;
}
else
{
p = null;
}

if (context.Result is IQueryable<T> q)
{
source = q;
Expand All @@ -64,9 +55,7 @@ public async Task InvokeAsync(IMiddlewareContext context)
filter.Accept(visitor);

source = source.Where(visitor.CreateFilter<T>());
context.Result = p is null
? (object)source
: new PageableData<T>(source, p.Properties);
context.Result = source;
}
}
}
Expand Down
Expand Up @@ -1094,7 +1094,7 @@ public virtual void Execute_Selection_Paging_OnlyMeta()

// act
var result = executor.Execute("{ foos { totalCount pageInfo {startCursor}}}")
as IReadOnlyQueryResult;
as IReadOnlyQueryResult;

// assert
Assert.NotNull(result);
Expand All @@ -1106,7 +1106,7 @@ public virtual void Execute_Selection_Paging_OnlyMeta()

var pageInfoResult = foosResult["pageInfo"] as IDictionary<string, object>;
Assert.NotNull(pageInfoResult);
Assert.Equal("eyJfX3RvdGFsQ291bnQiOjIsIl9fcG9zaXRpb24iOjB9",
Assert.Equal("MA==",
pageInfoResult["startCursor"]);

Assert.NotNull(resultCtx);
Expand Down
13 changes: 1 addition & 12 deletions src/Core/Types.Sorting/QueryableSortMiddleware.cs
Expand Up @@ -41,15 +41,6 @@ public async Task InvokeAsync(IMiddlewareContext context)
source = e.AsQueryable();
}

if (context.Result is PageableData<T> p)
{
source = p.Source;
}
else
{
p = null;
}

if (source != null
&& context.Field
.Arguments[SortObjectFieldDescriptorExtensions.OrderByArgumentName]
Expand All @@ -62,9 +53,7 @@ public async Task InvokeAsync(IMiddlewareContext context)
sortArgument.Accept(visitor);

source = visitor.Sort(source);
context.Result = p is null
? (object)source
: new PageableData<T>(source, p.Properties);
context.Result = source;
}
}
}
Expand Down
244 changes: 242 additions & 2 deletions src/Core/Types.Tests/Types/Relay/ConnectionTypeTests.cs
@@ -1,7 +1,11 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using HotChocolate.Execution;
using HotChocolate.Resolvers;
using Microsoft.Extensions.DependencyInjection;
using Snapshooter.Xunit;
using Xunit;

Expand Down Expand Up @@ -163,6 +167,189 @@ public async Task UsePaging_WithComplexType()
result.MatchSnapshot();
}

[Fact]
public void InferSchemaWithAttributesCorrectly()
{
SchemaBuilder.New()
.AddQueryType<QueryWithPagingAttribute>()
.Create()
.ToString()
.MatchSnapshot();
}

[Fact]
public async Task UsePagingAttribute_InMemory_Collection()
{
// arrange
ISchema schema = SchemaBuilder.New()
.AddQueryType<QueryWithPagingAttribute>()
.Create();

IQueryExecutor executor = schema.MakeExecutable();

string query = @"
{
collection {
edges {
cursor
node
}
pageInfo
{
hasNextPage
}
totalCount
}
}
";

// act
IExecutionResult result = await executor.ExecuteAsync(query);

// assert
result.MatchSnapshot();
}

[Fact]
public async Task UsePagingAttribute_InMemory_Queryable()
{
// arrange
ISchema schema = SchemaBuilder.New()
.AddQueryType<QueryWithPagingAttribute>()
.Create();

IQueryExecutor executor = schema.MakeExecutable();

string query = @"
{
queryable {
edges {
cursor
node
}
pageInfo
{
hasNextPage
}
totalCount
}
}
";

// act
IExecutionResult result = await executor.ExecuteAsync(query);

// assert
result.MatchSnapshot();
}

[Fact]
public async Task UsePagingAttribute_InMemory_Enumerable()
{
// arrange
ISchema schema = SchemaBuilder.New()
.AddQueryType<QueryWithPagingAttribute>()
.Create();

IQueryExecutor executor = schema.MakeExecutable();

string query = @"
{
enumerable {
edges {
cursor
node
}
pageInfo
{
hasNextPage
}
totalCount
}
}
";

// act
IExecutionResult result = await executor.ExecuteAsync(query);

// assert
result.MatchSnapshot();
}

[Fact]
public async Task ConnectionType_Without_Paging_Middleware()
{
// arrange
ISchema schema = SchemaBuilder.New()
.AddQueryType<QueryWithPagingAttribute>()
.Create();

IQueryExecutor executor = schema.MakeExecutable();

string query = @"
{
connectionOfString {
edges {
cursor
node
}
pageInfo
{
hasNextPage
}
totalCount
}
}
";

// act
IExecutionResult result = await executor.ExecuteAsync(query);

// assert
result.MatchSnapshot();
}

[Fact]
public async Task UsePagingAttribute_With_Injected_ConnectionResolver()
{
// arrange
ISchema schema = SchemaBuilder.New()
.AddQueryType<QueryWithPagingAttribute>()
.Create();

IQueryExecutor executor = schema.MakeExecutable();

string query = @"
{
enumerable {
edges {
cursor
node
}
pageInfo
{
hasNextPage
}
totalCount
}
}";

IServiceProvider services = new ServiceCollection()
.AddSingleton<IConnectionResolver<IEnumerable<string>>, ConnectionOfString>()
.BuildServiceProvider();

IReadOnlyQueryRequest request = QueryRequestBuilder.New()
.SetQuery(query)
.SetServices(services)
.Create();

// act
IExecutionResult result = await executor.ExecuteAsync(request);

// assert
result.MatchSnapshot();
}

public class QueryType
: ObjectType
{
Expand Down Expand Up @@ -217,7 +404,7 @@ public class FooType
IObjectTypeDescriptor<Foo> descriptor)
{
descriptor.Interface<FooInterfaceType>();
descriptor.Field(t => t.Bar).UsePaging<StringType>();
descriptor.Field<ICollection<string>>(t => t.Bar).UsePaging<StringType>();
}
}

Expand All @@ -238,5 +425,58 @@ public class Foo
public ICollection<string> Bar { get; } =
new List<string> { "a", "b", "c", "d", "e", "f", "g" };
}

public class QueryWithPagingAttribute
{
[UsePaging]
public ICollection<string> Collection { get; } =
new List<string> { "a", "b", "c", "d", "e", "f", "g" };

[UsePaging]
public IQueryable<string> Queryable { get; } =
new List<string> { "a", "b", "c", "d", "e", "f", "g" }.AsQueryable();

[UsePaging]
public IEnumerable<string> Enumerable { get; } =
new List<string> { "a", "b", "c", "d", "e", "f", "g" }.AsQueryable();

[GraphQLType(typeof(ConnectionWithCountType<StringType>))]
public Connection<string> ConnectionOfString(
int? first = null,
int? last = null,
string? after = null,
string? before = null) =>
new Connection<string>(
new PageInfo(false, false, "foo", "foo", 1),
new List<Edge<string>> { new Edge<string>("abc", "foo") });
}

public class ConnectionOfString : IConnectionResolver<IEnumerable<string>>

This comment has been minimized.

Copy link
@benmccallum

benmccallum May 21, 2020

Collaborator

Hey @michaelstaib, how is this IConnectionResolver<T> implementation used by a resolver?

Just above, lines 443 and 444, I would've expected to see that resolver method returning ConnectionOfString, or somehow associating this guy, but can't see that as the case.

This comment has been minimized.

Copy link
@benmccallum

benmccallum May 21, 2020

Collaborator

I see there's the middleware, which depends on an IConnectionResolver<T>, but just wondering what the signature of a resolver method would be. Pure code first I'd imagine wouldn't even need an implementation. Something like this I'd imagine?
public ConnectionOfString ConnectionOfStringUsingIConnectionResolver { get; }

{
public ValueTask<IConnection> ResolveAsync(
IMiddlewareContext context,
IEnumerable<string> source,
ConnectionArguments arguments = default,
bool withTotalCount = false,
CancellationToken cancellationToken = default)
{
return new ValueTask<IConnection>(new Connection<string>(
new PageInfo(false, false, "foo", "foo", 1),
new List<Edge<string>> { new Edge<string>("abc", "foo") }));
}

public ValueTask<IConnection> ResolveAsync(
IMiddlewareContext context,
object source,
ConnectionArguments arguments = default,
bool withTotalCount = false,
CancellationToken cancellationToken = default) =>
ResolveAsync(
context,
(IEnumerable<string>)source,
arguments,
withTotalCount,
cancellationToken);
}
}
}
6 changes: 3 additions & 3 deletions src/Core/Types.Tests/Types/Relay/EdgeTests.cs
Expand Up @@ -13,7 +13,7 @@ public class EdgeTests
{
// arrange
// act
var edge = new Edge<string>(cursor, node);
var edge = new Edge<string>(node, cursor);

// assert
Assert.Equal(cursor, edge.Cursor);
Expand All @@ -25,7 +25,7 @@ public void CreateEdge_CursorIsNull_ArgumentNullException()
{
// arrange
// act
Action a = () => new Edge<string>(null, "abc");
Action a = () => new Edge<string>("abc", null);

// assert
Assert.Throws<ArgumentException>(a);
Expand All @@ -36,7 +36,7 @@ public void CreateEdge_CursorIsEmpty_ArgumentNullException()
{
// arrange
// act
Action a = () => new Edge<string>(string.Empty, "abc");
Action a = () => new Edge<string>("abc", string.Empty);

// assert
Assert.Throws<ArgumentException>(a);
Expand Down

0 comments on commit 7e0b24b

Please sign in to comment.