Skip to content

Commit

Permalink
Implement async enumerable support for .net8 (#48)
Browse files Browse the repository at this point in the history
  • Loading branch information
wallymathieu authored Jun 7, 2024
1 parent 44b281d commit 29019c7
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 7 deletions.
9 changes: 9 additions & 0 deletions src/FullExample/MyController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ public IEnumerable<string> ActionYields10Responses(string value)
yield return "i:" + i;
}
}
public async IAsyncEnumerable<string> ActionYields10Responses2(string value)
{
yield return "invoking action on mycontroller with value : " + value;
for (int i = 0; i < 10; i++)
{
await Task.Delay(1000);
yield return "i:" + i;
}
}
public string Fail()
{
throw new Exception("Failure!");
Expand Down
56 changes: 49 additions & 7 deletions src/Isop/CommandLine/ArgumentInvoker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,45 @@ namespace Isop.CommandLine
using Domain;
using Isop.Help;
using Infrastructure;

using System.Threading;
using System.Reflection;

public class ArgumentInvoker(IServiceProvider serviceProvider, Recognizes recognizes, HelpController helpController)
{
private ILookup<string, ArgumentAction>? _recognizesMap;

private ILookup<string,ArgumentAction> RecognizesMap =>
_recognizesMap ??= recognizes.Properties.Where(p=>p.Action!=null).ToLookup(p=>p.Name, p=>p.Action);

#if NET8_0_OR_GREATER
private static bool IsIAsyncEnumerable(Type type) =>(
type.IsGenericType
&& type.GetGenericTypeDefinition() == typeof(IAsyncEnumerable<>));

private static readonly MethodInfo _readAsyncEnumerableMethod = typeof(ArgumentInvoker).GetMethods(BindingFlags.Static|BindingFlags.NonPublic).Single(m => m.Name.Equals(nameof(ReadAsyncEnumerable)));
private static async IAsyncEnumerable<object?> ReadAsyncEnumerable<T>(IAsyncEnumerable<T> enumerable)
{
await foreach (var val in enumerable)
{
yield return val;
}
}
private static bool AsyncEnumerable(object result, out IAsyncEnumerable<object?>? enumerable)
{
enumerable = null;
if (result is null) return false;
Type t= result.GetType();
if (IsIAsyncEnumerable(t)
|| t.GetInterfaces().Any(IsIAsyncEnumerable))
{
var argT = t.GetInterface("IAsyncEnumerable`1")!.GetGenericArguments();
enumerable = (IAsyncEnumerable<object?>)_readAsyncEnumerableMethod.MakeGenericMethod(argT).Invoke(null, [result])!;
return true;
}
return false;
}
#endif

public IEnumerable<Task<InvokeResult>> Invoke(ParsedArguments parsedArguments)
{
IEnumerable<Task<InvokeResult>> ChooseArgument(RecognizedArgument arg)
Expand Down Expand Up @@ -63,14 +94,25 @@ IEnumerable<Task<InvokeResult>> ChooseArgument(RecognizedArgument arg)
throw new Exception($"Unable to resolve {method.RecognizedClass.Name}");
var result = method.RecognizedAction.Invoke(instance,
method.RecognizedActionParameters.ToArray());
InvokeResult? res = result switch
InvokeResult? res;
if (result is Task task)
{
res= new InvokeResult.AsyncControllerAction(RunTask(task));
}
#if NET8_0_OR_GREATER
else if (AsyncEnumerable(result,out var enumerable))
{
res= new InvokeResult.ControllerAction(enumerable.ToBlockingEnumerable());
}
#endif
else
{
Task task=> new InvokeResult.AsyncControllerAction(RunTask(task)),
_ => new InvokeResult.ControllerAction(result),
};
res = new InvokeResult.ControllerAction(result);
}
return new []{
Task.FromResult( res)};
return [
Task.FromResult( res)];
});
return tasks;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#if NET8_0_OR_GREATER
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Threading.Tasks;
using Isop;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;

namespace Tests.ViewsAndRendering
{
[TestFixture]
public class Given_controller_returning_asyncenumerable
{
class EnumerableController<T>(Func<T> OnEnumerate,int Length)
{
public async IAsyncEnumerable<T> Return()
{
for (int i = 0; i < Length; i++)
{
await Task.CompletedTask;
yield return OnEnumerate();
}
}
}
class EnumerableIntController(Func<int> OnEnumerate,int Length):
EnumerableController<int>(OnEnumerate,Length) { }
class EnumerableObjectController(Func<object> OnEnumerate,int Length):
EnumerableController<object>(OnEnumerate,Length) { }

[Test]
public async Task It_understands_method_returning_enumerable_object()
{
var count = 0;
var sc = new ServiceCollection();
sc.AddSingleton(ci => new EnumerableObjectController(Length: 2, OnEnumerate: () => (count++)));

var arguments = AppHostBuilder.Create(sc)
.Recognize(typeof(EnumerableObjectController))
.BuildAppHost()
.Parse(new[] { "EnumerableObject", "Return" });

Assert.That(arguments.Unrecognized.Count(), Is.EqualTo(0));
var sw = new StringWriter();
await arguments.InvokeAsync(sw);
Assert.That(count, Is.EqualTo(2));
Assert.AreEqual(new []{"0","1"},sw.ToString().Split(new []{'\r','\n'}, StringSplitOptions.RemoveEmptyEntries));
}

[Test]
public async Task It_understands_method_returning_enumerable_int()
{
var count = 0;
var sc = new ServiceCollection();
sc.AddSingleton(ci => new EnumerableIntController(Length: 2, OnEnumerate: () => (count++)));

var arguments = AppHostBuilder.Create(sc)
.Recognize(typeof(EnumerableIntController))
.BuildAppHost()
.Parse(new[] { "EnumerableInt", "Return" });

Assert.That(arguments.Unrecognized.Count(), Is.EqualTo(0));
var sw = new StringWriter();
await arguments.InvokeAsync(sw);
Assert.That(count, Is.EqualTo(2));
Assert.AreEqual(new []{"0","1"},sw.ToString().Split(new []{'\r','\n'}, StringSplitOptions.RemoveEmptyEntries));
}
}
}
#endif

0 comments on commit 29019c7

Please sign in to comment.