From 09141d60a122de6c204e2dd2c2c8a4106c67c90c Mon Sep 17 00:00:00 2001 From: mark-keaton Date: Wed, 31 Jan 2024 07:36:08 -0600 Subject: [PATCH 1/2] Chain without TIn and TOut implemented. --- ChainSharp.Tests/Tests/WorkflowTests.cs | 32 +++++++++++ ChainSharp/Workflow.cs | 73 +++++++++++++++++++++++++ 2 files changed, 105 insertions(+) diff --git a/ChainSharp.Tests/Tests/WorkflowTests.cs b/ChainSharp.Tests/Tests/WorkflowTests.cs index d81ed0b..3ffeadf 100644 --- a/ChainSharp.Tests/Tests/WorkflowTests.cs +++ b/ChainSharp.Tests/Tests/WorkflowTests.cs @@ -45,6 +45,22 @@ protected override async Task>> RunI .Chain>() .Resolve(); } + + private class ChainTestWithNoInputs : Workflow> + { + protected override async Task>> RunInternal(Ingredients input) + { + var brew = new Brew(); + return Activate(input, "this is a test string to make sure it gets added to memory") + .Chain() + .Chain() + .Chain() + .Chain() + .Chain(brew) + .Chain() + .Resolve(); + } + } private class TwoTupleStepTest : Step<(Ingredients, BrewingJug), Unit> { @@ -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); + } } \ No newline at end of file diff --git a/ChainSharp/Workflow.cs b/ChainSharp/Workflow.cs index f204356..702b6df 100644 --- a/ChainSharp/Workflow.cs +++ b/ChainSharp/Workflow.cs @@ -1,3 +1,4 @@ +using System.Reflection; using ChainSharp.Exceptions; using ChainSharp.Extensions; using ChainSharp.Utils; @@ -86,7 +87,79 @@ public Workflow Chain(Either Chain() where TStep : IStep, new() => Chain(new TStep()); + + /// Chain() + public Workflow Chain() where TStep : new() + { + 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."); + } + var types = interfaceType.GetGenericArguments(); + var tIn = types[0]; + var tOut = types[1]; + + // Find a Generic Chain with 3 Type arguments and 1 Parameter + var methods = this.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(); + + var method = methods.FirstOrDefault(); // For example, if you know the count + + if (method == null) + { + throw new InvalidOperationException("Suitable 'Chain' method not found."); + } + + var genericMethod = method.MakeGenericMethod(typeof(TStep), tIn, tOut); + + var stepInstance = Activator.CreateInstance(typeof(TStep)); + var result = genericMethod.Invoke(this, new object[] { stepInstance }); + + return (Workflow)result; + } + + /// Chain(TStep) + public Workflow Chain(TStep stepInstance) where TStep : new() + { + 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."); + } + + var types = interfaceType.GetGenericArguments(); + var tIn = types[0]; + var tOut = types[1]; + + // Find a Generic Chain with 3 Type arguments and 1 Parameter + var methods = this.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(); + + var method = methods.FirstOrDefault(); // For example, if you know the count + + if (method == null) + { + throw new InvalidOperationException("Suitable 'Chain' method not found."); + } + + var genericMethod = method.MakeGenericMethod(typeof(TStep), tIn, tOut); + + var result = genericMethod.Invoke(this, new object[] { stepInstance }); + + return (Workflow)result; + } + + /// Chain(TStep, In) public Workflow Chain(TStep step, Either previousStep) From 57a3a0b30710feb192ed6ee93c9d076b2fce8f5e Mon Sep 17 00:00:00 2001 From: mark-keaton Date: Wed, 31 Jan 2024 11:09:33 -0600 Subject: [PATCH 2/2] ReflectionHelpers added. Increment version. --- ChainSharp/ChainSharp.csproj | 2 +- ChainSharp/Utils/ReflectionHelpers.cs | 66 +++++++++++++++++++++++++++ ChainSharp/Workflow.cs | 65 +++----------------------- 3 files changed, 74 insertions(+), 59 deletions(-) create mode 100644 ChainSharp/Utils/ReflectionHelpers.cs diff --git a/ChainSharp/ChainSharp.csproj b/ChainSharp/ChainSharp.csproj index 23de808..6877f29 100644 --- a/ChainSharp/ChainSharp.csproj +++ b/ChainSharp/ChainSharp.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 1.0.6 + 1.0.7 Theauxm.ChainSharp diff --git a/ChainSharp/Utils/ReflectionHelpers.cs b/ChainSharp/Utils/ReflectionHelpers.cs new file mode 100644 index 0000000..54159dc --- /dev/null +++ b/ChainSharp/Utils/ReflectionHelpers.cs @@ -0,0 +1,66 @@ +using System.Reflection; + +namespace ChainSharp.Utils; + +internal static class ReflectionHelpers +{ + /// + /// Given a well-formed IStep implementation, extract the two Type arguments + /// TIn and TOut from the interface. + /// + /// + /// + /// + public static (Type, Type) ExtractStepTypeArguments() + { + 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."); + } + + var types = interfaceType.GetGenericArguments(); + var tIn = types[0]; + var tOut = types[1]; + + return (tIn, tOut); + } + + /// + /// 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. + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static MethodInfo FindGenericChainMethod(Workflow 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; + } +} \ No newline at end of file diff --git a/ChainSharp/Workflow.cs b/ChainSharp/Workflow.cs index 702b6df..c2bf510 100644 --- a/ChainSharp/Workflow.cs +++ b/ChainSharp/Workflow.cs @@ -4,6 +4,7 @@ using ChainSharp.Utils; using LanguageExt; using LanguageExt.UnsafeValueAccess; +using static ChainSharp.Utils.ReflectionHelpers; namespace ChainSharp; @@ -91,71 +92,19 @@ public Workflow Chain() /// Chain() public Workflow Chain() where TStep : new() { - 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."); - } - - var types = interfaceType.GetGenericArguments(); - var tIn = types[0]; - var tOut = types[1]; - - // Find a Generic Chain with 3 Type arguments and 1 Parameter - var methods = this.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(); - - var method = methods.FirstOrDefault(); // For example, if you know the count - - if (method == null) - { - throw new InvalidOperationException("Suitable 'Chain' method not found."); - } - - var genericMethod = method.MakeGenericMethod(typeof(TStep), tIn, tOut); - + var (tIn, tOut) = ExtractStepTypeArguments(); + var chainMethod = FindGenericChainMethod(this, tIn, tOut); var stepInstance = Activator.CreateInstance(typeof(TStep)); - var result = genericMethod.Invoke(this, new object[] { stepInstance }); - + var result = chainMethod.Invoke(this, new object[] { stepInstance }); return (Workflow)result; } /// Chain(TStep) public Workflow Chain(TStep stepInstance) where TStep : new() { - 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."); - } - - var types = interfaceType.GetGenericArguments(); - var tIn = types[0]; - var tOut = types[1]; - - // Find a Generic Chain with 3 Type arguments and 1 Parameter - var methods = this.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(); - - var method = methods.FirstOrDefault(); // For example, if you know the count - - if (method == null) - { - throw new InvalidOperationException("Suitable 'Chain' method not found."); - } - - var genericMethod = method.MakeGenericMethod(typeof(TStep), tIn, tOut); - - var result = genericMethod.Invoke(this, new object[] { stepInstance }); - + var (tIn, tOut) = ExtractStepTypeArguments(); + var chainMethod = FindGenericChainMethod(this, tIn, tOut); + var result = chainMethod.Invoke(this, new object[] { stepInstance }); return (Workflow)result; }