feat(liquid): delete json filter and add filters already provided by fluid library (#5188)

Co-authored-by: Jean-Baptiste Dalle <jean-baptiste.dalle@stereograph.fr>
This commit is contained in:
jeanbaptistedalle 2024-04-26 15:50:05 +02:00 committed by GitHub
parent 406469825c
commit 09cda8ecc7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 114 additions and 51 deletions

View file

@ -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);
}
}

View file

@ -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
/// <summary>
/// Configures the Fluid options.
/// </summary>
public Action<FluidOptions> FluidOptions { get; set; } = _ => { };
public Action<FluidOptions> FluidOptions { get; set; } = options =>
{
options.FluidFiltersDelegate = context => context.Options.Filters
.WithArrayFilters()
.WithStringFilters()
.WithNumberFilters()
.WithMiscFilters();
};
/// <inheritdoc />
public override void Apply()
{
Services.Configure(FluidOptions);
Services
.AddHandlersFrom<ConfigureLiquidEngine>()
.AddScoped<ILiquidTemplateManager, LiquidTemplateManager>()
.AddScoped<LiquidParser>()
.AddExpressionDescriptorProvider<LiquidExpressionDescriptorProvider>()
.AddLiquidFilter<JsonFilter>("json")
.AddLiquidFilter<Base64Filter>("base64")
.AddLiquidFilter<DictionaryKeysFilter>("keys")
;
;
}
}

View file

@ -1,44 +0,0 @@
using System.Text.Json;
using Elsa.Liquid.Contracts;
using Fluid;
using Fluid.Values;
namespace Elsa.Liquid.Filters;
/// <summary>
/// A liquid filter that converts a value into a JSON string representation.
/// </summary>
public class JsonFilter : ILiquidFilter
{
public ValueTask<FluidValue> ProcessAsync(FluidValue input, FilterArguments arguments, TemplateContext context)
{
switch (input.Type)
{
case FluidValues.Array:
return new ValueTask<FluidValue>(new StringValue(JsonSerializer.Serialize(input.Enumerate(context).Select(o => o.ToObjectValue()))));
case FluidValues.Boolean:
return new ValueTask<FluidValue>(new StringValue(JsonSerializer.Serialize(input.ToBooleanValue())));
case FluidValues.Nil:
return new ValueTask<FluidValue>(FluidValue.Create("null", context.Options));
case FluidValues.Number:
return new ValueTask<FluidValue>(new StringValue(JsonSerializer.Serialize(input.ToNumberValue())));
case FluidValues.DateTime:
case FluidValues.Dictionary:
case FluidValues.Object:
return new ValueTask<FluidValue>(new StringValue(JsonSerializer.Serialize(input.ToObjectValue())));
case FluidValues.String:
var stringValue = input.ToStringValue();
return string.IsNullOrWhiteSpace(stringValue)
? new ValueTask<FluidValue>(input)
: new ValueTask<FluidValue>(new StringValue(JsonSerializer.Serialize(stringValue)));
}
throw new NotSupportedException("Unrecognized FluidValue");
}
}

View file

@ -12,13 +12,13 @@ public class FluidOptions
/// <summary>
/// A dictionary of filter registrations.
/// </summary>
public Dictionary<string, Type> FilterRegistrations { get; } = new();
public Dictionary<string, Type> FilterRegistrations { get; } = new();
/// <summary>
/// A list of parser configurations.
/// </summary>
public IList<Action<LiquidParser>> ParserConfiguration { get; } = new List<Action<LiquidParser>>();
/// <summary>
/// Gets or sets a value indicating whether to allow access to the configuration object.
/// </summary>
@ -28,4 +28,9 @@ public class FluidOptions
/// Gets or sets the default encoder to use when rendering a template.
/// </summary>
public TextEncoder Encoder { get; set; } = NullEncoder.Default;
/// <summary>
/// Get or set the fluid filters enabled in Elsa.
/// </summary>
public Action<TemplateContext> FluidFiltersDelegate { get; set; } = _ => { };
}

View file

@ -10,6 +10,7 @@
<ItemGroup>
<ProjectReference Include="..\..\..\src\common\Elsa.Testing.Shared\Elsa.Testing.Shared.csproj" />
<ProjectReference Include="..\..\..\src\modules\Elsa.Http\Elsa.Http.csproj" />
<ProjectReference Include="..\..\..\src\modules\Elsa.JavaScript\Elsa.JavaScript.csproj" />
<ProjectReference Include="..\..\..\src\modules\Elsa.ProtoActor\Elsa.ProtoActor.csproj" />
<ProjectReference Include="..\..\..\src\modules\Elsa.Scheduling\Elsa.Scheduling.csproj" />

View file

@ -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;
/// <summary>
/// A workflow that use javascript to get some data, use them with some liquid expressions
/// </summary>
public class JavascriptAndLiquidWorkflow : WorkflowBase
{
protected override void Build(IWorkflowBuilder builder)
{
var products = new Variable<object> { Name = "Products", StorageDriverType = typeof(WorkflowStorageDriver) };
var product = new Variable<object> { 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 }}")),
}
};
}
}

View file

@ -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<IWorkflowRunner>();
}
[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);
}
}