< Summary

Information
Class: Elsa.Workflows.Models.JsonActivityConstructorContextHelper
Assembly: Elsa.Workflows.Core
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Core/Models/ActivityConstructorContext.cs
Line coverage
81%
Covered lines: 79
Uncovered lines: 18
Coverable lines: 97
Total lines: 240
Line coverage: 81.4%
Branch coverage
80%
Covered branches: 55
Total branches: 68
Branch coverage: 80.8%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
Create(...)100%11100%
CreateActivity(...)100%11100%
CreateActivity(...)100%44100%
StripTypeMetadata(...)71.42%1602844.82%
ReadSyntheticInputs(...)81.25%241668.42%
ReadSyntheticOutputs(...)100%1010100%
GetBoolean(...)100%11100%
GetNullableBoolean(...)80%101091.66%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Core/Models/ActivityConstructorContext.cs

#LineLine coverage
 1using System.Text.Json;
 2using System.Text.Json.Nodes;
 3using Elsa.Expressions.Helpers;
 4using Elsa.Extensions;
 5using Elsa.Workflows.Attributes;
 6using Elsa.Workflows.Exceptions;
 7using Elsa.Workflows.Memory;
 8using Humanizer;
 9using Microsoft.Extensions.Logging;
 10
 11namespace Elsa.Workflows.Models;
 12
 13public record ActivityConstructorContext(ActivityDescriptor ActivityDescriptor, Func<Type, ActivityConstructionResult> A
 14{
 15    public ActivityConstructionResult<T> CreateActivity<T>() where T : IActivity => ActivityFactory(typeof(T)).Cast<T>()
 16    public ActivityConstructionResult CreateActivity(Type type) => ActivityFactory(type);
 17}
 18
 19public static class JsonActivityConstructorContextHelper
 20{
 21    public static ActivityConstructorContext Create(ActivityDescriptor activityDescriptor, JsonElement element, JsonSeri
 22    {
 1053923        return new ActivityConstructorContext(activityDescriptor, type => CreateActivity(activityDescriptor, type, eleme
 24    }
 25
 26    public static ActivityConstructionResult<T> CreateActivity<T>(ActivityDescriptor activityDescriptor, JsonElement ele
 27    {
 30628        return CreateActivity(activityDescriptor, typeof(T), element, serializerOptions).Cast<T>();
 29    }
 30
 31    public static ActivityConstructionResult CreateActivity(ActivityDescriptor activityDescriptor, Type type, JsonElemen
 32    {
 557333        var exceptions = new List<Exception>();
 34
 35        // 1) Grab the raw text
 557336        var raw = element.GetRawText();
 37
 38        // 2) Parse into a JsonNode so we can mutate
 557339        var node = JsonNode.Parse(raw)!;
 40
 41        // 3) Strip out any "_type" metadata wrappers
 557342        StripTypeMetadata(node);
 43
 44        // 4) Get a cleaned JSON string
 557345        var cleanedJson = node.ToJsonString(new(JsonSerializerDefaults.Web));
 46
 47        // 5) Parse it back to a JsonDocument so we can get a clean JsonElement
 557348        using var doc = JsonDocument.Parse(cleanedJson);
 557349        var cleanedElement = doc.RootElement.Clone();
 50
 51        // 6) Deserialize into IActivity, or create a plain instance
 557352        var activity = (IActivity)JsonSerializer.Deserialize(cleanedJson, type, serializerOptions)!;
 53
 54        // 7) Pull out your boolean flags from the cleaned element
 557355        var canStartWorkflow = GetBoolean(cleanedElement, "canStartWorkflow");
 557356        var runAsynchronously = GetNullableBoolean(cleanedElement, "runAsynchronously") ?? activityDescriptor.RunAsynchr
 57
 58        // 8) If composite, setup
 557359        if (activity is IComposite composite)
 43260            composite.Setup();
 61
 62        // 9) Existing synthetic inputs/outputs routines, using the cleanedElement
 557363        ReadSyntheticInputs(activityDescriptor, activity, cleanedElement, serializerOptions, exceptions);
 557364        ReadSyntheticOutputs(activityDescriptor, activity, cleanedElement);
 65
 66        // 10) Finally re‑apply those flags
 557367        activity.SetCanStartWorkflow(canStartWorkflow);
 557368        activity.SetRunAsynchronously(runAsynchronously);
 69
 557370        return new(activity, exceptions);
 557371    }
 72
 73    /// <summary>
 74    /// Recursively remove any "_type" properties and unwrap any
 75    /// { "_type": "...", "items": [ ... ] } or "values": [ ... ] wrappers
 76    /// </summary>
 77    private static void StripTypeMetadata(JsonNode node)
 78    {
 79        switch (node)
 80        {
 81            case JsonObject obj:
 82                // First recurse into each child
 79671983                foreach (var key in obj.Select(kvp => kvp.Key).ToList())
 84                {
 21642185                    var child = obj[key];
 21642186                    if (child is JsonObject wrapper && wrapper.ContainsKey("_type"))
 87                    {
 88                        // If it has an "items" array, replace the whole property with that array
 089                        if (wrapper["items"] is JsonArray items)
 90                        {
 091                            obj[key] = items;
 092                            StripTypeMetadata(items);
 093                            continue;
 94                        }
 95
 96                        // Or a "values" array
 097                        if (wrapper["values"] is JsonArray values)
 98                        {
 099                            obj[key] = values;
 0100                            StripTypeMetadata(values);
 0101                            continue;
 102                        }
 103                    }
 104
 105                    // Otherwise just recurse normally
 216421106                    if (child != null)
 203867107                        StripTypeMetadata(child);
 108                }
 109
 110                // And remove any stray _type on this object
 73728111                obj.Remove("_type");
 73728112                break;
 113
 114            case JsonArray arr:
 22376115                for (var i = 0; i < arr.Count; i++)
 116                {
 4767117                    var element = arr[i];
 4767118                    if (element is JsonObject w && w.ContainsKey("_type"))
 119                    {
 0120                        if (w["items"] is JsonArray items)
 121                        {
 0122                            arr[i] = items;
 0123                            StripTypeMetadata(items);
 0124                            continue;
 125                        }
 126
 0127                        if (w["values"] is JsonArray values)
 128                        {
 0129                            arr[i] = values;
 0130                            StripTypeMetadata(values);
 0131                            continue;
 132                        }
 133                    }
 134
 4767135                    if (element != null)
 4767136                        StripTypeMetadata(element);
 137                }
 138
 139                break;
 140
 141            // primitives—nothing to do
 142            default:
 143                break;
 144        }
 6421145    }
 146
 147    private static void ReadSyntheticInputs(ActivityDescriptor activityDescriptor, IActivity activity, JsonElement activ
 148    {
 21042149        foreach (var inputDescriptor in activityDescriptor.Inputs.Where(x => x.IsSynthetic))
 150        {
 37151            var inputName = inputDescriptor.Name;
 37152            var propertyName = inputName.Camelize();
 37153            var nakedType = inputDescriptor.Type;
 37154            var wrappedType = typeof(Input<>).MakeGenericType(nakedType);
 155
 37156            if (!activityRoot.TryGetProperty(propertyName, out var propertyElement) || propertyElement.ValueKind == Json
 157                continue;
 158
 37159            if(propertyElement.ValueKind == JsonValueKind.Object && !propertyElement.TryGetProperty("typeName", out var 
 160            {
 4161                var exception = new InvalidActivityDescriptorInputException(
 4162                    $"Activity descriptor '{activityDescriptor.Name}' has invalid input property '{propertyName}'; missi
 4163                );
 4164                exceptions.Add(exception);
 4165                continue;
 166            }
 167
 33168            var isWrapped = propertyElement.ValueKind == JsonValueKind.Object && propertyElement.GetProperty("typeName")
 169
 33170            if (isWrapped)
 171            {
 33172                var json = propertyElement.ToString();
 33173                var inputValue = JsonSerializer.Deserialize(json, wrappedType, options);
 174
 33175                activity.SyntheticProperties[inputName] = inputValue!;
 176            }
 177            else
 178            {
 0179                activity.SyntheticProperties[inputName] = propertyElement.ConvertTo(inputDescriptor.Type)!;
 180            }
 181        }
 5573182    }
 183
 184    private static void ReadSyntheticOutputs(ActivityDescriptor activityDescriptor, IActivity activity, JsonElement acti
 185    {
 14983186        foreach (var outputDescriptor in activityDescriptor.Outputs.Where(x => x.IsSynthetic))
 187        {
 103188            var outputName = outputDescriptor.Name;
 103189            var propertyName = outputName.Camelize();
 103190            var nakedType = outputDescriptor.Type;
 103191            var wrappedType = typeof(Output<>).MakeGenericType(nakedType);
 192
 103193            if (!activityRoot.TryGetProperty(propertyName, out var propertyElement) || propertyElement.ValueKind == Json
 194                continue;
 195
 33196            var memoryReferenceElement = propertyElement.GetProperty("memoryReference");
 197
 33198            if (!memoryReferenceElement.TryGetProperty("id", out var memoryReferenceIdElement))
 199                continue;
 200
 33201            var variable = new Variable
 33202            {
 33203                Id = memoryReferenceIdElement.GetString()!
 33204            };
 33205            variable.Name = variable.Id;
 206
 33207            var output = Activator.CreateInstance(wrappedType, variable)!;
 208
 33209            activity.SyntheticProperties[outputName] = output!;
 210        }
 5573211    }
 212
 213    private static bool GetBoolean(JsonElement element, string propertyName)
 214    {
 5573215        return GetNullableBoolean(element, propertyName) ?? false;
 216    }
 217
 218    private static bool? GetNullableBoolean(JsonElement element, string propertyName)
 219    {
 11146220        var propertyNames = new[]
 11146221        {
 11146222            propertyName.Camelize(), propertyName.Pascalize()
 11146223        };
 224
 36636225        foreach (var name in propertyNames)
 226        {
 12212227            if (element.TryGetProperty("customProperties", out var customPropertyElement))
 228            {
 10340229                if (customPropertyElement.TryGetProperty(name, out var canStartWorkflowElement))
 10080230                    return (bool?)canStartWorkflowElement.GetValue();
 231            }
 232
 2132233            if (element.TryGetProperty(propertyName.Camelize(), out var property)
 2132234                && (bool?)property.GetValue() is { } propValue)
 0235                return propValue;
 236        }
 237
 1066238        return null;
 239    }
 240}