From 952cbb4edc00f663273c122016a7f322bb28e11d Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Mon, 31 Mar 2025 18:11:14 +0200 Subject: [PATCH] Add strict type checking for variable parsing (#6536) * Add strict type checking for variable parsing Introduced a `StrictMode` flag for variables, enforcing stricter type validation during parsing and conversions. Updated related methods and tests to reflect the stricter parsing behavior, ensuring consistent type compatibility and error handling. * Refactor StrictMode handling and improve ObjectConverter logic Moved StrictMode flag from Variable to ObjectConverter for better cohesion and updated related references. Additionally, removed unused parameters and simplified ObjectConverterOptions to streamline configuration and maintain consistency across the codebase. * Refactor type conversion logic and update logging defaults Improve type conversion reliability by introducing `TryConvertValue` and refining exception handling. Update JSON scenarios to use "Inherit" as the default for log persistence modes, ensuring consistency across configurations. * Refactor exception handling in ObjectConverter. Introduce a helper method `ReturnOrThrow` to streamline and centralize exception handling logic. This change also adds support for non-strict mode, allowing value fallback instead of throwing exceptions when enabled. * Add numeric type checks and improve type conversion handling Introduce `IsNumericType` extension method to identify numeric types. Enhance type conversion logic in `ObjectConverter` to handle numeric, boolean, and string types more accurately. Update variable deserialization workflows to handle conversion failures gracefully. * Set a default comment for ObjectConverter.StrictMode assignment Added a comment clarifying that StrictMode is set to its default value. This improves code readability and helps maintainers understand the intent. * Fix formatting inconsistencies and improve code clarity Removed redundant whitespace and adjusted formatting to align with coding standards. These changes enhance the readability and maintainability of the code without altering functionality. * Fix typo in Program.cs variable comment Corrected a minor issue in the comment for `identityTokenSection` to remove the unnecessary "Modify" text. This change improves code readability and removes potential confusion for developers. * Simplify imports in ModifyVariableHandler.cs Removed unused `Microsoft.Extensions.Options` and `Elsa.Workflows.Options` imports to clean up dependencies and improve maintainability. This change reduces clutter without affecting the existing functionality. --- src/apps/Elsa.Server.Web/Program.cs | 4 ++ .../Extensions/HandlerExtensions.cs | 4 +- .../Elsa.Common/Extensions/TypeExtensions.cs | 10 ++++ .../Helpers/ObjectConverter.cs | 47 ++++++++++++++++--- .../Elsa.Expressions/Models/MemoryBlock.cs | 2 +- .../Models/MemoryBlockReference.cs | 6 +-- .../Elsa.Http/Activities/HttpEndpoint.cs | 1 - .../Activities/SetVariable.cs | 18 +++---- .../Extensions/DictionaryExtensions.cs | 16 +++++-- .../ExpressionExecutionContextExtensions.cs | 15 ++++-- .../Extensions/OutputExtensions.cs | 13 +++-- .../Extensions/VariableExtensions.cs | 16 +++---- .../Elsa.Workflows.Core/Memory/Variable.cs | 1 - .../Services/VariablePersistenceManager.cs | 6 ++- .../WorkflowInstanceStorageDriver.cs | 3 +- .../input-output-logging-1.json | 2 +- .../input-output-logging-2.json | 6 +-- .../input-output-logging-3-child.json | 8 ++-- .../ObjectConversion/Tests.cs | 9 +--- 19 files changed, 127 insertions(+), 60 deletions(-) diff --git a/src/apps/Elsa.Server.Web/Program.cs b/src/apps/Elsa.Server.Web/Program.cs index b2d1941ce..edc03cffd 100644 --- a/src/apps/Elsa.Server.Web/Program.cs +++ b/src/apps/Elsa.Server.Web/Program.cs @@ -15,6 +15,7 @@ using Elsa.EntityFrameworkCore.Modules.Identity; using Elsa.EntityFrameworkCore.Modules.Management; using Elsa.EntityFrameworkCore.Modules.Runtime; using Elsa.EntityFrameworkCore.Modules.Tenants; +using Elsa.Expressions.Helpers; using Elsa.Extensions; using Elsa.Features.Services; using Elsa.Identity.Multitenancy; @@ -52,6 +53,7 @@ using Elsa.Workflows.LogPersistence; using Elsa.Workflows.Management; using Elsa.Workflows.Management.Compression; using Elsa.Workflows.Management.Stores; +using Elsa.Workflows.Memory; using Elsa.Workflows.Options; using Elsa.Workflows.Runtime.Distributed.Extensions; using Elsa.Workflows.Runtime.Options; @@ -105,6 +107,8 @@ const bool disableVariableWrappers = false; const bool disableVariableCopying = false; const bool useManualOtelInstrumentation = true; +ObjectConverter.StrictMode = true; // Default. + var builder = WebApplication.CreateBuilder(args); var services = builder.Services; var configuration = builder.Configuration; diff --git a/src/common/Elsa.Mediator/Extensions/HandlerExtensions.cs b/src/common/Elsa.Mediator/Extensions/HandlerExtensions.cs index 2a157037f..cc5b55fd7 100644 --- a/src/common/Elsa.Mediator/Extensions/HandlerExtensions.cs +++ b/src/common/Elsa.Mediator/Extensions/HandlerExtensions.cs @@ -44,7 +44,7 @@ public static class HandlerExtensions /// The cancellation token. public static Task InvokeAsync(this INotificationHandler handler, MethodBase handleMethod, INotification notification, CancellationToken cancellationToken) { - return (Task)handleMethod.Invoke(handler, new object?[] { notification, cancellationToken })!; + return (Task)handleMethod.Invoke(handler, [notification, cancellationToken])!; } /// @@ -56,7 +56,7 @@ public static class HandlerExtensions /// The cancellation token. public static Task InvokeAsync(this ICommandHandler handler, MethodBase handleMethod, ICommand command, CancellationToken cancellationToken) { - var task = (Task)handleMethod.Invoke(handler, new object?[] { command, cancellationToken })!; + var task = (Task)handleMethod.Invoke(handler, [command, cancellationToken])!; return task; } } \ No newline at end of file diff --git a/src/modules/Elsa.Common/Extensions/TypeExtensions.cs b/src/modules/Elsa.Common/Extensions/TypeExtensions.cs index 5883bcd12..863a2057b 100644 --- a/src/modules/Elsa.Common/Extensions/TypeExtensions.cs +++ b/src/modules/Elsa.Common/Extensions/TypeExtensions.cs @@ -44,4 +44,14 @@ public static class TypeExtensions /// Returns the element type of the specified collection type. /// public static Type GetCollectionElementType(this Type type) => type.GenericTypeArguments[0]; + + /// + /// Determines whether the specified type is a numeric type. + /// + /// The type to check. + /// True if the specified type is numeric, otherwise false. + public static bool IsNumericType(this Type type) + { + return type.IsPrimitive || type == typeof(decimal) || type == typeof(float) || type == typeof(double) || type == typeof(int) || type == typeof(long) || type == typeof(short) || type == typeof(byte) || type == typeof(uint) || type == typeof(ulong) || type == typeof(ushort) || type == typeof(sbyte); + } } \ No newline at end of file diff --git a/src/modules/Elsa.Expressions/Helpers/ObjectConverter.cs b/src/modules/Elsa.Expressions/Helpers/ObjectConverter.cs index 767d57a60..0714ca238 100644 --- a/src/modules/Elsa.Expressions/Helpers/ObjectConverter.cs +++ b/src/modules/Elsa.Expressions/Helpers/ObjectConverter.cs @@ -1,6 +1,7 @@ using System.Collections; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.SymbolStore; using System.Dynamic; using System.Globalization; using System.Text.Encodings.Web; @@ -26,6 +27,8 @@ public record ObjectConverterOptions(JsonSerializerOptions? SerializerOptions = /// public static class ObjectConverter { + public static bool StrictMode = true; // Set to false to revert to original flexible behavior. + /// /// Attempts to convert the source value into the destination type. /// @@ -106,8 +109,19 @@ public static class ObjectConverter { if (jsonNode is not JsonArray jsonArray) { + var valueKind = jsonNode.GetValueKind(); + if (valueKind == JsonValueKind.Null) + return null; + + if (valueKind == JsonValueKind.Undefined) + return null; + return underlyingTargetType switch { + { } t when t == typeof(bool) && valueKind == JsonValueKind.False => false, + { } t when t == typeof(bool) && valueKind == JsonValueKind.True => true, + { } t when t.IsNumericType() && valueKind == JsonValueKind.Number => ConvertTo(jsonNode.ToString(), t), + { } t when t == typeof(string) && valueKind == JsonValueKind.String => jsonNode.ToString(), { } t when t == typeof(string) => jsonNode.ToString(), { } t when t == typeof(ExpandoObject) && jsonNode.GetValueKind() == JsonValueKind.Object => JsonSerializer.Deserialize(jsonNode.ToJsonString()), { } t when t != typeof(object) || converterOptions?.DeserializeJsonObjectToObject == true => jsonNode.Deserialize(targetType, serializerOptions), @@ -187,14 +201,22 @@ public static class ObjectConverter var targetTypeConverter = TypeDescriptor.GetConverter(underlyingTargetType); if (targetTypeConverter.CanConvertFrom(underlyingSourceType)) - return targetTypeConverter.IsValid(value) - ? targetTypeConverter.ConvertFrom(null, CultureInfo.InvariantCulture, value) - : targetType.GetDefaultValue(); + { + var isValid = targetTypeConverter.IsValid(value); + + if (isValid) + return targetTypeConverter.ConvertFrom(null, CultureInfo.InvariantCulture, value); + } var sourceTypeConverter = TypeDescriptor.GetConverter(underlyingSourceType); if (sourceTypeConverter.CanConvertTo(underlyingTargetType)) - return sourceTypeConverter.ConvertTo(value, underlyingTargetType); + { + var isValid = targetTypeConverter.IsValid(value); + + if (isValid) + return sourceTypeConverter.ConvertTo(value, underlyingTargetType); + } if (underlyingTargetType.IsEnum) { @@ -248,8 +270,8 @@ public static class ObjectConverter return collection; } } - - if(underlyingTargetType.IsArray) + + if (underlyingTargetType.IsArray) { var executedEnumerable = enumerable.Cast().ToList(); var underlyingTargetElementType = underlyingTargetType.GetElementType()!; @@ -261,6 +283,7 @@ public static class ObjectConverter array.SetValue(convertedItem, index); index++; } + return array; } } @@ -269,8 +292,20 @@ public static class ObjectConverter { return Convert.ChangeType(value, underlyingTargetType, CultureInfo.InvariantCulture); } + catch (FormatException e) + { + return ReturnOrThrow(e); + } catch (InvalidCastException e) { + return ReturnOrThrow(e); + } + + object ReturnOrThrow(Exception e) + { + if (!StrictMode) + return value; + throw new TypeConversionException($"Failed to convert an object of type {sourceType} to {underlyingTargetType}", value, underlyingTargetType, e); } } diff --git a/src/modules/Elsa.Expressions/Models/MemoryBlock.cs b/src/modules/Elsa.Expressions/Models/MemoryBlock.cs index 75cf2c1cd..58df59695 100644 --- a/src/modules/Elsa.Expressions/Models/MemoryBlock.cs +++ b/src/modules/Elsa.Expressions/Models/MemoryBlock.cs @@ -15,7 +15,7 @@ public class MemoryBlock /// /// Constructor. /// - public MemoryBlock(object? value, object? metadata = default) + public MemoryBlock(object? value, object? metadata = null) { Value = value; Metadata = metadata; diff --git a/src/modules/Elsa.Expressions/Models/MemoryBlockReference.cs b/src/modules/Elsa.Expressions/Models/MemoryBlockReference.cs index 1a6a908fd..9756e5959 100644 --- a/src/modules/Elsa.Expressions/Models/MemoryBlockReference.cs +++ b/src/modules/Elsa.Expressions/Models/MemoryBlockReference.cs @@ -22,7 +22,7 @@ public class MemoryBlockReference /// /// The ID of the memory block. /// - public string Id { get; set; } = default!; + public string Id { get; set; } = null!; /// /// Declares the memory block. @@ -62,7 +62,7 @@ public class MemoryBlockReference /// /// Sets the value of the memory block. /// - public void Set(MemoryRegister memoryRegister, object? value, Action? configure = default) + public void Set(MemoryRegister memoryRegister, object? value, Action? configure = null) { var block = GetBlock(memoryRegister); block.Value = value; @@ -72,7 +72,7 @@ public class MemoryBlockReference /// /// Sets the value of the memory block. /// - public void Set(ExpressionExecutionContext context, object? value, Action? configure = default) => context.Set(this, value, configure); + public void Set(ExpressionExecutionContext context, object? value, Action? configure = null) => context.Set(this, value, configure); /// /// Returns the pointed to by the specified memory block reference. diff --git a/src/modules/Elsa.Http/Activities/HttpEndpoint.cs b/src/modules/Elsa.Http/Activities/HttpEndpoint.cs index 428d73adc..edfcfa5c3 100644 --- a/src/modules/Elsa.Http/Activities/HttpEndpoint.cs +++ b/src/modules/Elsa.Http/Activities/HttpEndpoint.cs @@ -225,7 +225,6 @@ public class HttpEndpoint : Trigger // Handle Form Fields if (request.HasFormContentType) { - var formFields = request.Form.ToObjectDictionary(); ParsedContent.Set(context, formFields); diff --git a/src/modules/Elsa.Workflows.Core/Activities/SetVariable.cs b/src/modules/Elsa.Workflows.Core/Activities/SetVariable.cs index 914933d23..a12b35086 100644 --- a/src/modules/Elsa.Workflows.Core/Activities/SetVariable.cs +++ b/src/modules/Elsa.Workflows.Core/Activities/SetVariable.cs @@ -18,37 +18,37 @@ namespace Elsa.Workflows.Activities; public class SetVariable : CodeActivity { /// - public SetVariable([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) + public SetVariable([CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : base(source, line) { } /// - public SetVariable(Variable variable, Input value, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : this(source, line) + public SetVariable(Variable variable, Input value, [CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : this(source, line) { Variable = variable; Value = value; } /// - public SetVariable(Variable variable, Variable value, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) + public SetVariable(Variable variable, Variable value, [CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : this(variable, new Input(value), source, line) { } /// - public SetVariable(Variable variable, Func value, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) + public SetVariable(Variable variable, Func value, [CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : this(variable, new Input(value), source, line) { } /// - public SetVariable(Variable variable, Func value, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) + public SetVariable(Variable variable, Func value, [CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : this(variable, new Input(value), source, line) { } /// - public SetVariable(Variable variable, T value, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) + public SetVariable(Variable variable, T value, [CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : this(variable, new Input(value), source, line) { } @@ -57,7 +57,7 @@ public class SetVariable : CodeActivity /// The variable to assign the value to. /// [Input(Description = "The variable to assign the value to.")] - public Variable Variable { get; set; } = default!; + public Variable Variable { get; set; } = null!; /// /// The value to assign. @@ -81,7 +81,7 @@ public class SetVariable : CodeActivity public class SetVariable : CodeActivity { /// - public SetVariable([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) + public SetVariable([CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : base(source, line) { } @@ -89,7 +89,7 @@ public class SetVariable : CodeActivity /// The variable to assign the value to. /// [Input(Description = "The variable to assign the value to.")] - public Variable Variable { get; set; } = default!; + public Variable Variable { get; set; } = null!; /// /// The value to assign. diff --git a/src/modules/Elsa.Workflows.Core/Extensions/DictionaryExtensions.cs b/src/modules/Elsa.Workflows.Core/Extensions/DictionaryExtensions.cs index 33cb94f5f..9aae63257 100644 --- a/src/modules/Elsa.Workflows.Core/Extensions/DictionaryExtensions.cs +++ b/src/modules/Elsa.Workflows.Core/Extensions/DictionaryExtensions.cs @@ -1,4 +1,5 @@ using Elsa.Expressions.Helpers; +using Elsa.Expressions.Models; // ReSharper disable once CheckNamespace namespace Elsa.Extensions; @@ -29,8 +30,9 @@ public static class DictionaryExtensions return false; } - value = ConvertValue(item); - return true; + var result = TryConvertValue(item); + value = result.Success ? (T)result.Value! : default!; + return result.Success; } public static bool TryGetValue(this IDictionary dictionary, IEnumerable keys, out T value) @@ -39,8 +41,9 @@ public static class DictionaryExtensions { if (dictionary.TryGetValue(key, out var item)) { - value = ConvertValue(item); - return true; + var result = TryConvertValue(item); + value = result.Success ? (T)result.Value! : default!; + return result.Success; } } @@ -98,4 +101,9 @@ public static class DictionaryExtensions } private static T? ConvertValue(object? value) => value.ConvertTo(); + + private static Result TryConvertValue(object? value) + { + return value.TryConvertTo(); + } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Extensions/ExpressionExecutionContextExtensions.cs b/src/modules/Elsa.Workflows.Core/Extensions/ExpressionExecutionContextExtensions.cs index 70f5381d2..aba7ae1ac 100644 --- a/src/modules/Elsa.Workflows.Core/Extensions/ExpressionExecutionContextExtensions.cs +++ b/src/modules/Elsa.Workflows.Core/Extensions/ExpressionExecutionContextExtensions.cs @@ -8,7 +8,9 @@ using Elsa.Workflows; using Elsa.Workflows.Activities; using Elsa.Workflows.Memory; using Elsa.Workflows.Models; +using Elsa.Workflows.Options; using Humanizer; +using Microsoft.Extensions.Options; // ReSharper disable once CheckNamespace namespace Elsa.Extensions; @@ -137,8 +139,7 @@ public static class ExpressionExecutionContextExtensions /// /// Creates a named variable in the context. /// - public static Variable CreateVariable(this ExpressionExecutionContext context, string name, T? value, Type? storageDriverType = null, - Action? configure = null) + public static Variable CreateVariable(this ExpressionExecutionContext context, string name, T? value, Type? storageDriverType = null, Action? configure = null) { var existingVariable = context.GetVariable(name, localScopeOnly: true); @@ -149,12 +150,14 @@ public static class ExpressionExecutionContextExtensions { StorageDriverType = storageDriverType ?? typeof(WorkflowInstanceStorageDriver) }; + + var parsedValue = variable.ParseValue(value); // Find the first parent context that has a variable container. // If not found, use the current context. var variableContainerContext = context.GetVariableContainerContext(); - variableContainerContext.Set(variable, value, configure); + variableContainerContext.Set(variable, parsedValue, configure); return variable; } @@ -184,7 +187,8 @@ public static class ExpressionExecutionContextExtensions var contextWithVariable = context.FindContextContainingBlock(variable.Id) ?? context; // Set the value on the variable. - variable.Set(contextWithVariable, value, configure); + var parsedValue = variable.ParseValue(value); + variable.Set(contextWithVariable, parsedValue, configure); // Return the variable. return variable; @@ -199,7 +203,8 @@ public static class ExpressionExecutionContextExtensions { // Set the value on the output. var outputMemoryBlockReference = output.MemoryBlockReference(); - context.Set(outputMemoryBlockReference, value, configure); + var parsedValue = output.ParseValue(value); + context.Set(outputMemoryBlockReference, parsedValue, configure); // If the referenced output is a workflow output definition, set the value on the workflow execution context. var workflowExecutionContext = context.GetWorkflowExecutionContext(); diff --git a/src/modules/Elsa.Workflows.Core/Extensions/OutputExtensions.cs b/src/modules/Elsa.Workflows.Core/Extensions/OutputExtensions.cs index 70573c4d2..5d1c8ae9d 100644 --- a/src/modules/Elsa.Workflows.Core/Extensions/OutputExtensions.cs +++ b/src/modules/Elsa.Workflows.Core/Extensions/OutputExtensions.cs @@ -1,4 +1,5 @@ using System.Runtime.CompilerServices; +using Elsa.Expressions.Helpers; using Elsa.Expressions.Models; using Elsa.Workflows; using Elsa.Workflows.Memory; @@ -20,7 +21,7 @@ public static class OutputExtensions /// /// Sets the output to the specified value. /// - public static void Set(this Output? output, ActivityExecutionContext context, T? value, [CallerArgumentExpression("output")] string? outputName = default) => context.Set(output, value, outputName); + public static void Set(this Output? output, ActivityExecutionContext context, T? value, [CallerArgumentExpression("output")] string? outputName = null) => context.Set(output, value, outputName); /// /// Sets the output to the specified value. @@ -45,10 +46,10 @@ public static class OutputExtensions var memoryBlockReference = output?.MemoryBlockReference(); if (memoryBlockReference is null) - return default; + return null; if(!context.ExpressionExecutionContext.TryGetBlock(memoryBlockReference, out var memoryBlock)) - return default; + return null; var parsedContentVariableType = (memoryBlock.Metadata as VariableBlockMetadata)?.Variable.GetType(); return parsedContentVariableType?.GenericTypeArguments.FirstOrDefault(); @@ -62,4 +63,10 @@ public static class OutputExtensions var memoryBlockReference = output?.MemoryBlockReference(); return memoryBlockReference is not null && context.ExpressionExecutionContext.TryGetBlock(memoryBlockReference, out _); } + + public static object? ParseValue(this Output output, object? value) + { + var genericType = output.GetType(); + return VariableExtensions.ParseValue(genericType, value); + } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Extensions/VariableExtensions.cs b/src/modules/Elsa.Workflows.Core/Extensions/VariableExtensions.cs index 125040e79..81af7820d 100644 --- a/src/modules/Elsa.Workflows.Core/Extensions/VariableExtensions.cs +++ b/src/modules/Elsa.Workflows.Core/Extensions/VariableExtensions.cs @@ -66,13 +66,7 @@ public static class VariableExtensions public static void Set(this Variable variable, ActivityExecutionContext context, object? value) { - // Validate type compatibility. - if (!variable.TryParseValue(value, out var parsedValue)) - { - var variableType = variable.GetVariableType(); - throw new InvalidCastException($"The value '{value}' is not compatible with the variable '{variable.Name}' of type '{variableType.FullName}'."); - } - + var parsedValue = variable.ParseValue(value); // Set the value. ((MemoryBlockReference)variable).Set(context, parsedValue); } @@ -83,7 +77,13 @@ public static class VariableExtensions [RequiresUnreferencedCode("Calls System.Text.Json.JsonSerializer.Serialize(TValue, JsonSerializerOptions)")] public static object? ParseValue(this Variable variable, object? value) { - var genericType = variable.GetType().GenericTypeArguments.FirstOrDefault(); + var genericType = variable.GetType(); + return ParseValue(genericType, value); + } + + public static object? ParseValue(Type type, object? value) + { + var genericType = type.GenericTypeArguments.FirstOrDefault(); var converterOptions = new ObjectConverterOptions(SerializerOptions); return genericType == null ? value : value?.ConvertTo(genericType, converterOptions); } diff --git a/src/modules/Elsa.Workflows.Core/Memory/Variable.cs b/src/modules/Elsa.Workflows.Core/Memory/Variable.cs index 47a33dd29..c75655fc3 100644 --- a/src/modules/Elsa.Workflows.Core/Memory/Variable.cs +++ b/src/modules/Elsa.Workflows.Core/Memory/Variable.cs @@ -1,4 +1,3 @@ -using System.Text.Json.Serialization; using Elsa.Expressions.Helpers; using Elsa.Expressions.Models; using Humanizer; diff --git a/src/modules/Elsa.Workflows.Core/Services/VariablePersistenceManager.cs b/src/modules/Elsa.Workflows.Core/Services/VariablePersistenceManager.cs index 1e19a9d88..a383253e2 100644 --- a/src/modules/Elsa.Workflows.Core/Services/VariablePersistenceManager.cs +++ b/src/modules/Elsa.Workflows.Core/Services/VariablePersistenceManager.cs @@ -1,3 +1,4 @@ +using Elsa.Expressions.Helpers; using Elsa.Expressions.Models; using Elsa.Extensions; using Elsa.Workflows.Memory; @@ -51,10 +52,13 @@ public class VariablePersistenceManager(IStorageDriverManager storageDriverManag if (!variable.TryParseValue(value, out var parsedValue)) { logger.LogWarning("Failed to parse value for variable {VariableId} of type {VariableType} with value {Value}", variable.Id, variable.GetVariableType().FullName, value); + + if (!ObjectConverter.StrictMode) + variable.Set(register, value); continue; } - variable.Set(register, parsedValue); + variable.Set(register, parsedValue); } catch (Exception e) { diff --git a/src/modules/Elsa.Workflows.Core/VariableStorageDrivers/WorkflowInstanceStorageDriver.cs b/src/modules/Elsa.Workflows.Core/VariableStorageDrivers/WorkflowInstanceStorageDriver.cs index cf942dcce..566473c3a 100644 --- a/src/modules/Elsa.Workflows.Core/VariableStorageDrivers/WorkflowInstanceStorageDriver.cs +++ b/src/modules/Elsa.Workflows.Core/VariableStorageDrivers/WorkflowInstanceStorageDriver.cs @@ -47,7 +47,8 @@ public class WorkflowInstanceStorageDriver(IPayloadSerializer payloadSerializer) DeserializeJsonObjectToObject = true, SerializerOptions = payloadSerializer.GetOptions() }; - var parsedValue = node.ConvertTo(variableType, options); + var result = node.TryConvertTo(variableType, options); + var parsedValue = result.Success ? result.Value : node; return new (parsedValue); } diff --git a/test/component/Elsa.Workflows.ComponentTests/Scenarios/LogPersistenceModes/input-output-logging-1.json b/test/component/Elsa.Workflows.ComponentTests/Scenarios/LogPersistenceModes/input-output-logging-1.json index 4fe00dd76..f228d1ed5 100644 --- a/test/component/Elsa.Workflows.ComponentTests/Scenarios/LogPersistenceModes/input-output-logging-1.json +++ b/test/component/Elsa.Workflows.ComponentTests/Scenarios/LogPersistenceModes/input-output-logging-1.json @@ -79,7 +79,7 @@ "logPersistenceMode": { "default": "Exclude", "inputs": { - "text": "Default" + "text": "Inherit" }, "outputs": {} }, diff --git a/test/component/Elsa.Workflows.ComponentTests/Scenarios/LogPersistenceModes/input-output-logging-2.json b/test/component/Elsa.Workflows.ComponentTests/Scenarios/LogPersistenceModes/input-output-logging-2.json index a0e6fd9d3..cd2c977b3 100644 --- a/test/component/Elsa.Workflows.ComponentTests/Scenarios/LogPersistenceModes/input-output-logging-2.json +++ b/test/component/Elsa.Workflows.ComponentTests/Scenarios/LogPersistenceModes/input-output-logging-2.json @@ -52,7 +52,7 @@ "logPersistenceMode": { "default": "Include", "inputs": { - "text": "Default" + "text": "Inherit" }, "outputs": {} }, @@ -117,7 +117,7 @@ "version": 1, "customProperties": { "logPersistenceMode": { - "default": "Default", + "default": "Inherit", "inputs": { "text": "Include" }, @@ -154,7 +154,7 @@ "version": 1, "customProperties": { "logPersistenceMode": { - "default": "Default", + "default": "Inherit", "inputs": { "text": "Exclude" }, diff --git a/test/component/Elsa.Workflows.ComponentTests/Scenarios/LogPersistenceModes/input-output-logging-3-child.json b/test/component/Elsa.Workflows.ComponentTests/Scenarios/LogPersistenceModes/input-output-logging-3-child.json index dbdfb410b..47bc32840 100644 --- a/test/component/Elsa.Workflows.ComponentTests/Scenarios/LogPersistenceModes/input-output-logging-3-child.json +++ b/test/component/Elsa.Workflows.ComponentTests/Scenarios/LogPersistenceModes/input-output-logging-3-child.json @@ -75,10 +75,10 @@ "canStartWorkflow": false, "runAsynchronously": false, "logPersistenceMode": { - "default": "Default", + "default": "Inherit", "inputs": { - "outputName": "Default", - "outputValue": "Default" + "outputName": "Inherit", + "outputValue": "Inherit" }, "outputs": {} } @@ -120,7 +120,7 @@ "canStartWorkflow": false, "runAsynchronously": false, "logPersistenceMode": { - "default": "Default", + "default": "Inherit", "inputs": { "outputName": "Include", "outputValue": "Include" diff --git a/test/unit/Elsa.Workflows.Core.UnitTests/ObjectConversion/Tests.cs b/test/unit/Elsa.Workflows.Core.UnitTests/ObjectConversion/Tests.cs index 6e0f9034a..c1e228222 100644 --- a/test/unit/Elsa.Workflows.Core.UnitTests/ObjectConversion/Tests.cs +++ b/test/unit/Elsa.Workflows.Core.UnitTests/ObjectConversion/Tests.cs @@ -46,13 +46,8 @@ public class Tests var result = value.TryConvertTo(); // Assert - - // I would have expected this conversion to not be successful. It seems there are many cases like this - //Assert.False(result.Success); - //Assert.NotNull(result.Exception); - - Assert.True(result.Success); - Assert.Equal(0, result.Value); + Assert.False(result.Success); + Assert.NotNull(result.Exception); } [Fact]