Skip to content

Commit

Permalink
Merge pull request #7 from Theauxm/feature/workflow_no_inputs
Browse files Browse the repository at this point in the history
Chain without TIn and TOut implemented.
  • Loading branch information
mark-keaton authored Jan 31, 2024
2 parents 056a8ee + 57a3a0b commit 6a27ed5
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 1 deletion.
32 changes: 32 additions & 0 deletions ChainSharp.Tests/Tests/WorkflowTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,22 @@ protected override async Task<Either<WorkflowException, List<GlassBottle>>> RunI
.Chain<Bottle, BrewingJug, List<GlassBottle>>()
.Resolve();
}

private class ChainTestWithNoInputs : Workflow<Ingredients, List<GlassBottle>>
{
protected override async Task<Either<WorkflowException, List<GlassBottle>>> RunInternal(Ingredients input)
{
var brew = new Brew();
return Activate(input, "this is a test string to make sure it gets added to memory")
.Chain<Prepare>()
.Chain<Ferment>()
.Chain<TwoTupleStepTest>()
.Chain<ThreeTupleStepTest>()
.Chain(brew)
.Chain<Bottle>()
.Resolve();
}
}

private class TwoTupleStepTest : Step<(Ingredients, BrewingJug), Unit>
{
Expand Down Expand Up @@ -87,4 +103,20 @@ public async Task TestChain()

var result = await workflow.Run(ingredients);
}

[Theory]
public async Task TestChainWithNoInputs()
{
var workflow = new ChainTestWithNoInputs();

var ingredients = new Ingredients()
{
Apples = 1,
BrownSugar = 1,
Cinnamon = 1,
Yeast = 1
};

var result = await workflow.Run(ingredients);
}
}
2 changes: 1 addition & 1 deletion ChainSharp/ChainSharp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>1.0.6</Version>
<Version>1.0.7</Version>
<AssemblyName>Theauxm.ChainSharp</AssemblyName>
</PropertyGroup>

Expand Down
66 changes: 66 additions & 0 deletions ChainSharp/Utils/ReflectionHelpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System.Reflection;

namespace ChainSharp.Utils;

internal static class ReflectionHelpers
{
/// <summary>
/// Given a well-formed IStep implementation, extract the two Type arguments
/// TIn and TOut from the interface.
/// </summary>
/// <typeparam name="TStep"></typeparam>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
public static (Type, Type) ExtractStepTypeArguments<TStep>()
{
var stepType = typeof(TStep);
var interfaceType = stepType.GetInterfaces()
.FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IStep<,>));
if (interfaceType == null)
{
throw new InvalidOperationException($"{nameof(TStep)} does not implement IStep<TIn, TOut>.");
}

var types = interfaceType.GetGenericArguments();
var tIn = types[0];
var tOut = types[1];

return (tIn, tOut);
}

/// <summary>
/// Given the provided TIn and TOut type arguments, find a Chain method
/// implementation that can be invoked from the Workflow. Instantiate (prime?)
/// the method with the TIn and TOut arguments for actual invocation.
/// </summary>
/// <param name="workflow"></param>
/// <param name="tIn"></param>
/// <param name="tOut"></param>
/// <typeparam name="TStep"></typeparam>
/// <typeparam name="TInput"></typeparam>
/// <typeparam name="TReturn"></typeparam>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
public static MethodInfo FindGenericChainMethod<TStep, TInput, TReturn>(Workflow<TInput, TReturn> workflow,
Type tIn, Type tOut)
{
// Find a Generic Chain with 3 Type arguments and 1 Parameter
var methods = workflow.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public)
.Where(m => m is { Name: "Chain", IsGenericMethodDefinition: true })
.Where(m => m.GetGenericArguments().Length == 3)
.Where(m => m.GetParameters().Length == 1)
.ToList();

switch (methods.Count)
{
case > 1:
throw new InvalidOperationException("More than one Generic 'Chain' method found.");
case 0:
throw new InvalidOperationException("Suitable 'Chain' method not found.");
}

var method = methods.First();
var genericMethod = method.MakeGenericMethod(typeof(TStep), tIn, tOut);
return genericMethod;
}
}
22 changes: 22 additions & 0 deletions ChainSharp/Workflow.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using System.Reflection;
using ChainSharp.Exceptions;
using ChainSharp.Extensions;
using ChainSharp.Utils;
using LanguageExt;
using LanguageExt.UnsafeValueAccess;
using static ChainSharp.Utils.ReflectionHelpers;

namespace ChainSharp;

Expand Down Expand Up @@ -86,7 +88,27 @@ public Workflow<TInput, TReturn> Chain<TStep, TIn, TOut>(Either<WorkflowExceptio
public Workflow<TInput, TReturn> Chain<TStep, TIn, TOut>()
where TStep : IStep<TIn, TOut>, new()
=> Chain<TStep, TIn, TOut>(new TStep());

/// Chain<TStep>()
public Workflow<TInput, TReturn> Chain<TStep>() where TStep : new()
{
var (tIn, tOut) = ExtractStepTypeArguments<TStep>();
var chainMethod = FindGenericChainMethod<TStep, TInput, TReturn>(this, tIn, tOut);
var stepInstance = Activator.CreateInstance(typeof(TStep));
var result = chainMethod.Invoke(this, new object[] { stepInstance });
return (Workflow<TInput, TReturn>)result;
}

/// Chain<TStep>(TStep)
public Workflow<TInput, TReturn> Chain<TStep>(TStep stepInstance) where TStep : new()
{
var (tIn, tOut) = ExtractStepTypeArguments<TStep>();
var chainMethod = FindGenericChainMethod<TStep, TInput, TReturn>(this, tIn, tOut);
var result = chainMethod.Invoke(this, new object[] { stepInstance });
return (Workflow<TInput, TReturn>)result;
}



/// Chain<TStep, TIn>(TStep, In)
public Workflow<TInput, TReturn> Chain<TStep, TIn>(TStep step, Either<WorkflowException, TIn> previousStep)
Expand Down

0 comments on commit 6a27ed5

Please sign in to comment.