From 09cda8ecc7a2cc736035e5d2d2b4ecb7b29a1196 Mon Sep 17 00:00:00 2001 From: jeanbaptistedalle Date: Fri, 26 Apr 2024 15:50:05 +0200 Subject: [PATCH] feat(liquid): delete json filter and add filters already provided by fluid library (#5188) Co-authored-by: Jean-Baptiste Dalle --- .../Extensions/TemplateContextExtensions.cs | 2 + .../Elsa.Liquid/Features/LiquidFeature.cs | 15 ++++-- src/modules/Elsa.Liquid/Filters/JsonFilter.cs | 44 ---------------- .../Elsa.Liquid/Options/FluidOptions.cs | 11 ++-- .../Elsa.Workflows.IntegrationTests.csproj | 1 + .../JavascriptAndLiquidWorkflow.cs | 42 ++++++++++++++++ .../Scenarios/HttpRequestWithLiquid/Tests.cs | 50 +++++++++++++++++++ 7 files changed, 114 insertions(+), 51 deletions(-) delete mode 100644 src/modules/Elsa.Liquid/Filters/JsonFilter.cs create mode 100644 test/integration/Elsa.Workflows.IntegrationTests/Scenarios/HttpRequestWithLiquid/JavascriptAndLiquidWorkflow.cs create mode 100644 test/integration/Elsa.Workflows.IntegrationTests/Scenarios/HttpRequestWithLiquid/Tests.cs diff --git a/src/modules/Elsa.Liquid/Extensions/TemplateContextExtensions.cs b/src/modules/Elsa.Liquid/Extensions/TemplateContextExtensions.cs index 1f1da87fe..44aa4dc5f 100644 --- a/src/modules/Elsa.Liquid/Extensions/TemplateContextExtensions.cs +++ b/src/modules/Elsa.Liquid/Extensions/TemplateContextExtensions.cs @@ -1,6 +1,7 @@ using Elsa.Liquid.Contracts; using Elsa.Liquid.Options; using Fluid; +using Fluid.Filters; using Microsoft.Extensions.DependencyInjection; // ReSharper disable once CheckNamespace @@ -18,5 +19,6 @@ internal static class TemplateContextExtensions return filter.ProcessAsync(input, arguments, ctx); }); } + options.FluidFiltersDelegate(templateContext); } } \ No newline at end of file diff --git a/src/modules/Elsa.Liquid/Features/LiquidFeature.cs b/src/modules/Elsa.Liquid/Features/LiquidFeature.cs index 614d86403..e78db56b4 100644 --- a/src/modules/Elsa.Liquid/Features/LiquidFeature.cs +++ b/src/modules/Elsa.Liquid/Features/LiquidFeature.cs @@ -11,6 +11,7 @@ using Elsa.Liquid.Handlers; using Elsa.Liquid.Options; using Elsa.Liquid.Providers; using Elsa.Liquid.Services; +using Fluid.Filters; using Microsoft.Extensions.DependencyInjection; namespace Elsa.Liquid.Features; @@ -31,21 +32,27 @@ public class LiquidFeature : FeatureBase /// /// Configures the Fluid options. /// - public Action FluidOptions { get; set; } = _ => { }; + public Action FluidOptions { get; set; } = options => + { + options.FluidFiltersDelegate = context => context.Options.Filters + .WithArrayFilters() + .WithStringFilters() + .WithNumberFilters() + .WithMiscFilters(); + }; /// public override void Apply() { Services.Configure(FluidOptions); - + Services .AddHandlersFrom() .AddScoped() .AddScoped() .AddExpressionDescriptorProvider() - .AddLiquidFilter("json") .AddLiquidFilter("base64") .AddLiquidFilter("keys") - ; + ; } } \ No newline at end of file diff --git a/src/modules/Elsa.Liquid/Filters/JsonFilter.cs b/src/modules/Elsa.Liquid/Filters/JsonFilter.cs deleted file mode 100644 index 599ccea3d..000000000 --- a/src/modules/Elsa.Liquid/Filters/JsonFilter.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System.Text.Json; -using Elsa.Liquid.Contracts; -using Fluid; -using Fluid.Values; - -namespace Elsa.Liquid.Filters; - -/// -/// A liquid filter that converts a value into a JSON string representation. -/// -public class JsonFilter : ILiquidFilter -{ - public ValueTask ProcessAsync(FluidValue input, FilterArguments arguments, TemplateContext context) - { - switch (input.Type) - { - case FluidValues.Array: - return new ValueTask(new StringValue(JsonSerializer.Serialize(input.Enumerate(context).Select(o => o.ToObjectValue())))); - - case FluidValues.Boolean: - return new ValueTask(new StringValue(JsonSerializer.Serialize(input.ToBooleanValue()))); - - case FluidValues.Nil: - return new ValueTask(FluidValue.Create("null", context.Options)); - - case FluidValues.Number: - return new ValueTask(new StringValue(JsonSerializer.Serialize(input.ToNumberValue()))); - - case FluidValues.DateTime: - case FluidValues.Dictionary: - case FluidValues.Object: - return new ValueTask(new StringValue(JsonSerializer.Serialize(input.ToObjectValue()))); - - case FluidValues.String: - var stringValue = input.ToStringValue(); - - return string.IsNullOrWhiteSpace(stringValue) - ? new ValueTask(input) - : new ValueTask(new StringValue(JsonSerializer.Serialize(stringValue))); - } - - throw new NotSupportedException("Unrecognized FluidValue"); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Liquid/Options/FluidOptions.cs b/src/modules/Elsa.Liquid/Options/FluidOptions.cs index ff94ae5e7..68a1e3d8b 100644 --- a/src/modules/Elsa.Liquid/Options/FluidOptions.cs +++ b/src/modules/Elsa.Liquid/Options/FluidOptions.cs @@ -12,13 +12,13 @@ public class FluidOptions /// /// A dictionary of filter registrations. /// - public Dictionary FilterRegistrations { get; } = new(); - + public Dictionary FilterRegistrations { get; } = new(); + /// /// A list of parser configurations. /// public IList> ParserConfiguration { get; } = new List>(); - + /// /// Gets or sets a value indicating whether to allow access to the configuration object. /// @@ -28,4 +28,9 @@ public class FluidOptions /// Gets or sets the default encoder to use when rendering a template. /// public TextEncoder Encoder { get; set; } = NullEncoder.Default; + + /// + /// Get or set the fluid filters enabled in Elsa. + /// + public Action FluidFiltersDelegate { get; set; } = _ => { }; } \ No newline at end of file diff --git a/test/integration/Elsa.Workflows.IntegrationTests/Elsa.Workflows.IntegrationTests.csproj b/test/integration/Elsa.Workflows.IntegrationTests/Elsa.Workflows.IntegrationTests.csproj index 681d9d2a6..21ae7dbff 100644 --- a/test/integration/Elsa.Workflows.IntegrationTests/Elsa.Workflows.IntegrationTests.csproj +++ b/test/integration/Elsa.Workflows.IntegrationTests/Elsa.Workflows.IntegrationTests.csproj @@ -10,6 +10,7 @@ + diff --git a/test/integration/Elsa.Workflows.IntegrationTests/Scenarios/HttpRequestWithLiquid/JavascriptAndLiquidWorkflow.cs b/test/integration/Elsa.Workflows.IntegrationTests/Scenarios/HttpRequestWithLiquid/JavascriptAndLiquidWorkflow.cs new file mode 100644 index 000000000..9fef479d9 --- /dev/null +++ b/test/integration/Elsa.Workflows.IntegrationTests/Scenarios/HttpRequestWithLiquid/JavascriptAndLiquidWorkflow.cs @@ -0,0 +1,42 @@ +using Elsa.Expressions.Models; +using Elsa.JavaScript.Activities; +using Elsa.Workflows.Activities; +using Elsa.Workflows.Contracts; +using Elsa.Workflows.Memory; +using Elsa.Workflows.Services; + +namespace Elsa.Workflows.IntegrationTests.Scenarios.LiquidLists; + +/// +/// A workflow that use javascript to get some data, use them with some liquid expressions +/// +public class JavascriptAndLiquidWorkflow : WorkflowBase +{ + protected override void Build(IWorkflowBuilder builder) + { + var products = new Variable { Name = "Products", StorageDriverType = typeof(WorkflowStorageDriver) }; + var product = new Variable { Name = "Product", StorageDriverType = typeof(WorkflowStorageDriver) }; + + builder.Root = new Sequence + { + Variables = { products, product }, + Activities = + { + new RunJavaScript + { + Script = new(@"setProducts([{""id"":1, ""price"":12.99}, {""id"":2, ""price"":10}, {""id"":3, ""price"":1}])") + }, + new WriteLine(new Expression("Liquid", "First product id: {{ Variables.Products[0].id }}")), + new WriteLine(new Expression("Liquid", "First product price rounded: {{ Variables.Products[0].price | round }}")), + new WriteLine(new Expression("Liquid", "First product as json: {{ Variables.Products[0] | json }}")), + new WriteLine(new Expression("Liquid", "Second product id: {{ Variables.Products[1].id }}")), + new RunJavaScript + { + Script = new(@"setProduct({""id"":2, ""price"":10})") + }, + new WriteLine(new Expression("Liquid", "Single product id: {{ Variables.Product.id }}")), + new WriteLine(new Expression("Liquid", "Single product as json: {{ Variables.Product | json }}")), + } + }; + } +} \ No newline at end of file diff --git a/test/integration/Elsa.Workflows.IntegrationTests/Scenarios/HttpRequestWithLiquid/Tests.cs b/test/integration/Elsa.Workflows.IntegrationTests/Scenarios/HttpRequestWithLiquid/Tests.cs new file mode 100644 index 000000000..b05aa9d45 --- /dev/null +++ b/test/integration/Elsa.Workflows.IntegrationTests/Scenarios/HttpRequestWithLiquid/Tests.cs @@ -0,0 +1,50 @@ +using Elsa.Extensions; +using Elsa.Testing.Shared; +using Elsa.Workflows.Contracts; +using Elsa.Workflows.Models; +using Microsoft.Extensions.DependencyInjection; +using Xunit; +using Xunit.Abstractions; + +namespace Elsa.Workflows.IntegrationTests.Scenarios.LiquidLists; + +public sealed class Tests : IDisposable +{ + private readonly IWorkflowRunner _workflowRunner; + private readonly CapturingTextWriter _capturingTextWriter = new(); + private readonly IServiceProvider _services; + + public Tests(ITestOutputHelper testOutputHelper) + { + _services = new TestApplicationBuilder(testOutputHelper) + .WithCapturingTextWriter(_capturingTextWriter) + .ConfigureElsa(configure => configure.UseHttp()) + .Build(); + _workflowRunner = _services.GetRequiredService(); + } + + [Fact] + public async Task GetProducts() + { + await _services.PopulateRegistriesAsync(); + RunWorkflowResult result = await _workflowRunner.RunAsync(new JavascriptAndLiquidWorkflow()); + Assert.Equal(WorkflowStatus.Finished, result.WorkflowState.Status); + Assert.Empty(result.WorkflowState.Incidents); + Assert.Equal(WorkflowSubStatus.Finished, result.WorkflowState.SubStatus); + + var lines = _capturingTextWriter.Lines.ToList(); + Assert.Contains("First product id: 1", lines); + Assert.Contains("First product price rounded: 13", lines); + Assert.Contains("First product as json: {\"id\":1,\"price\":12.99}", lines); + Assert.Contains("Second product id: 2", lines); + + Assert.Contains("Single product id: 2", lines); + Assert.Contains("Single product as json: {\"id\":2,\"price\":10}", lines); + } + + public void Dispose() + { + _capturingTextWriter.Dispose(); + GC.SuppressFinalize(this); + } +} \ No newline at end of file