Skip to content

Commit

Permalink
Add support for IExecutable to Data (#2527)
Browse files Browse the repository at this point in the history
  • Loading branch information
PascalSenn committed Nov 4, 2020
1 parent 5a1d67d commit ad6cf46
Show file tree
Hide file tree
Showing 134 changed files with 3,617 additions and 326 deletions.
43 changes: 42 additions & 1 deletion src/HotChocolate/Core/src/Abstractions/IExecutable.cs
@@ -1,12 +1,53 @@
using System.Collections;
using System.Threading;
using System.Threading.Tasks;

#nullable enable

namespace HotChocolate
{
/// <summary>
/// Represents a abstract executable that is well known in the framework. If the execution
/// engine encounters a <see cref="IExecutable"/>, it will call execute it
/// </summary>
public interface IExecutable
{
ValueTask<object> ExecuteAsync(CancellationToken cancellationToken);
/// <summary>
/// The current state of the executable
/// </summary>
object Source { get; }

/// <summary>
/// Executes the executable and returns a list
/// </summary>
/// <param name="cancellationToken">
/// A cancellation token that can be used to cancel the execution.
/// </param>
/// <returns>Returns a arbitrary list</returns>
ValueTask<IList> ToListAsync(CancellationToken cancellationToken);

/// <summary>
/// Returns the first element of a sequence, or a default value if the sequence contains no
/// elements.
/// </summary>
/// <param name="cancellationToken">
/// A cancellation token that can be used to cancel the execution.
/// </param>
/// <returns>Returns the result</returns>
ValueTask<object?> FirstOrDefaultAsync(CancellationToken cancellationToken);

/// <summary>
/// Returns the only element of a default value if no such element exists. This method
/// throws an exception if more than one element satisfies the condition.
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
ValueTask<object?> SingleOrDefaultAsync(CancellationToken cancellationToken);

/// <summary>
/// Prints the executable in its current state
/// </summary>
/// <returns>A string that represents the executables state</returns>
string Print();
}
}
Expand Up @@ -3,7 +3,6 @@
using System.Text;
using System.Threading.Tasks;
using HotChocolate.Execution;
using HotChocolate.Execution.Batching;
using HotChocolate.Execution.Serialization;

namespace HotChocolate
Expand Down
Expand Up @@ -92,7 +92,7 @@ private async ValueTask ExecuteResolverPipelineAsync(CancellationToken cancellat
switch (_context.Result)
{
case IExecutable executable:
_context.Result = await executable.ExecuteAsync(cancellationToken);
_context.Result = await executable.ToListAsync(cancellationToken);
break;

case IQueryable queryable:
Expand Down
Expand Up @@ -19,17 +19,14 @@ public QueryableCursorPagingHandler(PagingOptions options)
object source,
CursorPagingArguments arguments)
{
if (source is IQueryable<TEntity> queryable)
CancellationToken ct = context.RequestAborted;
return source switch
{
return ResolveAsync(queryable, arguments, context.RequestAborted);
}

if (source is IEnumerable<TEntity> enumerable)
{
return ResolveAsync(enumerable.AsQueryable(), arguments, context.RequestAborted);
}

throw new GraphQLException("Cannot handle the specified data source.");
IQueryable<TEntity> q => ResolveAsync(q, arguments, ct),
IEnumerable<TEntity> e => ResolveAsync(e.AsQueryable(), arguments, ct),
IExecutable<TEntity> ex => SliceAsync(context, ex.Source, arguments),
_ => throw new GraphQLException("Cannot handle the specified data source.")
};
}

private async ValueTask<Connection> ResolveAsync(
Expand Down
Expand Up @@ -21,18 +21,25 @@ public QueryableOffsetPagingHandler(PagingOptions options)
{
}

protected override async ValueTask<CollectionSegment> SliceAsync(
protected override ValueTask<CollectionSegment> SliceAsync(
IResolverContext context,
object source,
OffsetPagingArguments arguments)
{
IQueryable<TItemType> queryable = source switch
return source switch
{
IQueryable<TItemType> q => q,
IEnumerable<TItemType> e => e.AsQueryable(),
IQueryable<TItemType> q => ResolveAsync(context, q, arguments),
IEnumerable<TItemType> e => ResolveAsync(context,e.AsQueryable(),arguments),
IExecutable<TItemType> ex => SliceAsync(context, ex.Source, arguments),
_ => throw new GraphQLException("Cannot handle the specified data source.")
};
}

private async ValueTask<CollectionSegment> ResolveAsync(
IResolverContext context,
IQueryable<TItemType> queryable,
OffsetPagingArguments arguments = default)
{
IQueryable<TItemType> original = queryable;

if (arguments.Skip.HasValue)
Expand Down
32 changes: 19 additions & 13 deletions src/HotChocolate/Core/test/Execution.Tests/CodeFirstTests.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -285,7 +286,7 @@ public string GetTest()

public IExecutable<string> GetQuery()
{
return MockQuery<string>.From("foo", "bar");
return new MockExecutable<string>(new []{ "foo", "bar" }.AsQueryable());
}

public string TestProp => "Hello World!";
Expand Down Expand Up @@ -461,31 +462,36 @@ public class DogType
}
}

public class MockQuery<T> : IExecutable<T>
public class MockExecutable<T> : IExecutable<T>
{
private readonly IReadOnlyList<T> _list;
private readonly IQueryable<T> _source;

private MockQuery(IEnumerable<T> list)
public MockExecutable(IQueryable<T> source)
{
_list = list.ToArray();
_source = source;
}

async ValueTask<object> IExecutable.ExecuteAsync(CancellationToken cancellationToken)
public object Source =>_source;

public ValueTask<IList> ToListAsync(CancellationToken cancellationToken)
{
return await ExecuteAsync(cancellationToken);
return new ValueTask<IList>(_source.ToList());
}

public ValueTask<IReadOnlyList<T>> ExecuteAsync(CancellationToken cancellationToken)
public ValueTask<object?> FirstOrDefaultAsync(CancellationToken cancellationToken)
{
return new ValueTask<IReadOnlyList<T>>(_list);
return new ValueTask<object?>(_source.FirstOrDefault());
}

public string Print()
public ValueTask<object?> SingleOrDefaultAsync(CancellationToken cancellationToken)
{
return _list.ToString();
return new ValueTask<object?>(_source.SingleOrDefault());
}

public static MockQuery<T> From(params T[] items) => new MockQuery<T>(items);
public string Print()
{
return _source.ToString();
}
}
}
}
@@ -1,29 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="Current">

<PropertyGroup>
<AssemblyName>HotChocolate.Types.Tests</AssemblyName>
<RootNamespace>HotChocolate</RootNamespace>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Core\HotChocolate.Core.csproj" />
<ProjectReference Include="..\Types.Tests.Documentation\HotChocolate.Types.Tests.Documentation.csproj" />
<ProjectReference Include="..\Utilities\HotChocolate.Tests.Utilities.csproj" />
</ItemGroup>

<ItemGroup>
<None Update="$(MSBuildProjectDirectory)\__resources__\*.*">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="$(MSBuildProjectDirectory)\xunit.runner.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>

<!--For Visual Studio for Mac Test Explorer we need this reference here-->
<ItemGroup>
<PackageReference Include="System.ComponentModel.Annotations" Version="4.7.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
</ItemGroup>

</Project>
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="Current">

<PropertyGroup>
<AssemblyName>HotChocolate.Types.Tests</AssemblyName>
<RootNamespace>HotChocolate</RootNamespace>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Core\HotChocolate.Core.csproj"/>
<ProjectReference Include="..\Types.Tests.Documentation\HotChocolate.Types.Tests.Documentation.csproj"/>
<ProjectReference Include="..\Utilities\HotChocolate.Tests.Utilities.csproj"/>
</ItemGroup>

<ItemGroup>
<None Update="$(MSBuildProjectDirectory)\__resources__\*.*">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="$(MSBuildProjectDirectory)\xunit.runner.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>

<!--For Visual Studio for Mac Test Explorer we need this reference here-->
<ItemGroup>
<PackageReference Include="System.ComponentModel.Annotations" Version="4.7.0"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1"/>
</ItemGroup>

</Project>
@@ -1,4 +1,7 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using HotChocolate.Execution;
Expand Down Expand Up @@ -410,6 +413,44 @@ await executor
.MatchSnapshotAsync();
}

[Fact]
public async Task Executable_With_Field_Settings()
{
Snapshot.FullName();

IRequestExecutor executor =
await new ServiceCollection()
.AddGraphQL()
.AddQueryType<ExecutableQueryType>()
.Services
.BuildServiceProvider()
.GetRequestExecutorAsync();

await executor
.ExecuteAsync(@"
{
fooExecutable {
edges {
node {
bar
}
cursor
}
nodes {
bar
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
totalCount
}
}")
.MatchSnapshotAsync();
}

[Fact]
public async Task Attribute_Nested_List_With_Field_Settings()
{
Expand Down Expand Up @@ -595,6 +636,22 @@ protected override void Configure(IObjectTypeDescriptor<Query> descriptor)
}
}

public class ExecutableQueryType : ObjectType<ExecutableQuery>
{
protected override void Configure(IObjectTypeDescriptor<ExecutableQuery> descriptor)
{
descriptor
.Field(t => t.FoosExecutable())
.Name("fooExecutable")
.UsePaging(
options: new PagingOptions
{
MaxPageSize = 2,
IncludeTotalCount = true
});
}
}

public class Query
{
public string[] Letters => new[]
Expand Down Expand Up @@ -623,6 +680,19 @@ public class Query
};
}

public class ExecutableQuery
{
public IExecutable<Foo> FoosExecutable() => new MockExecutable<Foo>(new List<Foo>
{
new Foo { Bar = "a" },
new Foo { Bar = "b" },
new Foo { Bar = "c" } ,
new Foo { Bar = "d" },
new Foo { Bar = "e" },
new Foo { Bar = "f" }
}.AsQueryable());
}

public class Foo
{
public string Bar { get; set; } = default!;
Expand Down Expand Up @@ -675,4 +745,36 @@ public interface ISome2
public string[] ExplicitType();
}
}

public class MockExecutable<T> : IExecutable<T>
{
private readonly IQueryable<T> _source;

public MockExecutable(IQueryable<T> source)
{
_source = source;
}

public object Source =>_source;

public ValueTask<IList> ToListAsync(CancellationToken cancellationToken)
{
return new ValueTask<IList>(_source.ToList());
}

public ValueTask<object?> FirstOrDefaultAsync(CancellationToken cancellationToken)
{
return new ValueTask<object?>(_source.FirstOrDefault());
}

public ValueTask<object?> SingleOrDefaultAsync(CancellationToken cancellationToken)
{
return new ValueTask<object?>(_source.SingleOrDefault());
}

public string Print()
{
return _source.ToString();
}
}
}

0 comments on commit ad6cf46

Please sign in to comment.